Compare commits
1450 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1b9f49b06 | |||
| 8f2857cf55 | |||
| f149fede71 | |||
| 972f072346 | |||
| 3cab3c1828 | |||
| 88bf7d0244 | |||
| a7523c7b54 | |||
| 590f328e07 | |||
| ca2682fb22 | |||
| 2bd1d4a423 | |||
| b2baa711c2 | |||
| 3d51f96dda | |||
| b55675e5a5 | |||
| 9a33992a0b | |||
| 07ae37eb5f | |||
| c36726f25f | |||
| e2e9abeb4c | |||
| 0fcc2215f7 | |||
| ce559c4643 | |||
| 8d4b13f633 | |||
| 19a3378fa3 | |||
| cef1250199 | |||
| 3c85658931 | |||
| ba7022bc2d | |||
| fb24abc111 | |||
| 0e3fb84e7c | |||
| 5d6d949ca4 | |||
| 3578182343 | |||
| 28bad2d963 | |||
| 384bd239fa | |||
| 98710ad296 | |||
| 4806e12907 | |||
| 9097760a0f | |||
| a5639bff60 | |||
| 1d66ed84f3 | |||
| 249ac3ea1c | |||
| b02db8020d | |||
| 4105540686 | |||
| 36d10c5cfd | |||
| f77bd76a14 | |||
| cef548ce7d | |||
| 5efd19ef7c | |||
| a0f4ef4e19 | |||
| 28cd4fd91c | |||
| 304239edd0 | |||
| 82f4089f42 | |||
| 00ef4d2cfc | |||
| cd33448446 | |||
| ddd70b0ff5 | |||
| e5b50a7073 | |||
| 83da8df554 | |||
| 5a1baadf3b | |||
| 10bd352bf4 | |||
| cbea45e6e0 | |||
| e5c6e4fa82 | |||
| f05457fd2c | |||
| bc6875fe9f | |||
| 1572e03dd1 | |||
| 131a1fae59 | |||
| 61025763ed | |||
| ef1c48e395 | |||
| 00905e4167 | |||
| b91830b089 | |||
| 9572c4157e | |||
| fef49eb32c | |||
| a5a895e42d | |||
| c0dc5bcdb5 | |||
| ed1d19532b | |||
| d50fbcdf4f | |||
| 9c1b19a1c7 | |||
| bf990517dc | |||
| 0cf7f952e2 | |||
| 83873c8c92 | |||
| 18a3516e04 | |||
| a61e6ba67f | |||
| 94d881cc18 | |||
| e081580786 | |||
| 8927211ea2 | |||
| 813905ca40 | |||
| 12a8317dd8 | |||
| 66be761d18 | |||
| f1e5b29908 | |||
| 6b6fa5bd40 | |||
| 119e03fda8 | |||
| 943ef4f9b0 | |||
| e98908a63a | |||
| 78d9a1c23c | |||
| 32bc7354ba | |||
| 342d022c43 | |||
| 1d36f4b821 | |||
| 4e459168df | |||
| a8c14ee839 | |||
| 5384807b85 | |||
| 23c40efe0d | |||
| 71e1ff778e | |||
| a76941ea17 | |||
| 5e05b2117e | |||
| 0c392d2092 | |||
| e5f0423a90 | |||
| 46caf49f18 | |||
| 7dc8d65235 | |||
| 74c87ec308 | |||
| 604a8312ef | |||
| 8b318caa0b | |||
| 159bcc7aa6 | |||
| 6bad1c3c7c | |||
| c45cf6aaff | |||
| 1333d4ed02 | |||
| a3bebde83c | |||
| d3e91b04a2 | |||
| 48f46ec4c3 | |||
| f4400c0130 | |||
| d596f1af5a | |||
| cc5d677d67 | |||
| 2f37be430b | |||
| e4c3cbc99b | |||
| 771c86229d | |||
| 5c1db00d7e | |||
| cff536710e | |||
| cd91f4bdb9 | |||
| acfe5b988b | |||
| 79860bc665 | |||
| 7d255f4be7 | |||
| 4623ba6fba | |||
| be658a1b31 | |||
| 1f4acdc99e | |||
| 2fe31055c8 | |||
| 0307282dbd | |||
| 85130ad882 | |||
| 7dbc2a1a78 | |||
| 2e18e1f8a4 | |||
| 12d42c4247 | |||
| 3215912d0c | |||
| 65ad7657ef | |||
| a22759136e | |||
| b8ebbac2b8 | |||
| 9c4590db07 | |||
| 7ba8a57793 | |||
| 2dc16f4bb8 | |||
| b68496a82a | |||
| 6892fa34d8 | |||
| 4eb3368875 | |||
| cdbfd3400c | |||
| c7c3e8033b | |||
| cc524810e8 | |||
| 4478e6823a | |||
| b6f593c8d0 | |||
| ca54fc581c | |||
| ab5bffea87 | |||
| 596f6f5668 | |||
| e7372cb7f2 | |||
| 8a48828a76 | |||
| feac0428ef | |||
| 7f83f9e37f | |||
| c2b70e23e4 | |||
| c8e5b7541d | |||
| eb46a4365c | |||
| 638b87950d | |||
| 058a8f7974 | |||
| 62b6476420 | |||
| b5585f548a | |||
| 16b16bae32 | |||
| e4a3e1a1a2 | |||
| b872e695b0 | |||
| 674a15ef32 | |||
| 8365f77c67 | |||
| d82d665280 | |||
| 54c88e189e | |||
| 752db42b3b | |||
| 41dae0daf4 | |||
| 10aaf85a26 | |||
| 596be2cf7f | |||
| 5c97ef9416 | |||
| ddba01e3bd | |||
| 5fc84299f1 | |||
| 96f675ae9c | |||
| 2eeeebf7c2 | |||
| ae3c189089 | |||
| 155ad537a9 | |||
| 0ff5bcd8fa | |||
| 37d421ad8a | |||
| 4a1430c62a | |||
| 52a4ed8193 | |||
| 8cc0d038bd | |||
| 211f11b363 | |||
| 6c637bf78c | |||
| 165759398e | |||
| f846836d5d | |||
| 43690156e3 | |||
| 1091d4e086 | |||
| 71cc0b8916 | |||
| 0523f13dfb | |||
| b703ddaae8 | |||
| 5ae9d5d3f1 | |||
| 061ea05935 | |||
| 256c04f5bb | |||
| ca6fdccdb2 | |||
| fb31ecc012 | |||
| a9dcc78db6 | |||
| c2d7d96f9f | |||
| 1f6c173e18 | |||
| 741bdd682c | |||
| 70a8da7682 | |||
| b593e8b57b | |||
| 7ccfb69e8d | |||
| 18615bad33 | |||
| 1ec00de03c | |||
| d9af85ce86 | |||
| 8f82436421 | |||
| bad7fbadda | |||
| f42a80e56c | |||
| c2651d2e5f | |||
| 7a506fc15e | |||
| 44bb977d1a | |||
| dec2c3a23e | |||
| 71bbd9ac8f | |||
| 2093541c37 | |||
| 4beb497032 | |||
| 40c7ba4305 | |||
| 56d4a9b69d | |||
| a95bca31e2 | |||
| b6115faf7c | |||
| 4fdb677103 | |||
| a541e45a53 | |||
| 34cf8e2b26 | |||
| 93f686af14 | |||
| b20c66b311 | |||
| 1332770df4 | |||
| 48ab6d7c7e | |||
| 729e1f105c | |||
| 08cecd8a0e | |||
| 42678fce89 | |||
| 6aa96a2ae9 | |||
| 79f29254ca | |||
| 30f9d310e5 | |||
| cc34ca510c | |||
| a285c54be2 | |||
| 5093c98656 | |||
| b7119fe248 | |||
| f4b70d4e71 | |||
| 7b9f7d5acd | |||
| eeb36f43a4 | |||
| 5a59a6d378 | |||
| b0d642f5e3 | |||
| 4397784749 | |||
| c0045d17e2 | |||
| 1eeba2d648 | |||
| 8cff5b3d40 | |||
| 2302f39d73 | |||
| 114af0c6d2 | |||
| 42963d3ee5 | |||
| 9069287cd8 | |||
| ca9b127b8b | |||
| 2106836b2c | |||
| cf5cf5b574 | |||
| 34240a2609 | |||
| eed1f41b29 | |||
| 99c6aa19b3 | |||
| 9598254d18 | |||
| da05525cbb | |||
| 0bc89cda8c | |||
| 11a9b0200e | |||
| a38b244a68 | |||
| 636fe2b27c | |||
| 29603388a2 | |||
| b9d3486f5b | |||
| 09cc099dcd | |||
| 4f2c02704f | |||
| 93e0d51872 | |||
| 776a410312 | |||
| 2028f8167f | |||
| 03269e703a | |||
| 9772b8123c | |||
| 31dfe7e295 | |||
| 7c3985fea4 | |||
| 9c18aa7f3b | |||
| bc85e59352 | |||
| 68a294eb71 | |||
| 33c61f6131 | |||
| 5dd055a43f | |||
| 937877fa7a | |||
| 8f994bbea7 | |||
| 6aca6600d7 | |||
| e7aba90808 | |||
| b2fa67c189 | |||
| 271bb32115 | |||
| 7bb0771768 | |||
| 76309c28d2 | |||
| 1b55c5534f | |||
| 98de71c086 | |||
| 9556fcdf5a | |||
| 68764a4403 | |||
| 0084ff260f | |||
| 0c729944b0 | |||
| 22ccd3d59e | |||
| 4c87b5a032 | |||
| 7a5d4c6e9f | |||
| 9c530ec470 | |||
| 358459f37a | |||
| 563b029801 | |||
| 7005cb1e76 | |||
| c79216fd14 | |||
| 2fda023607 | |||
| a52d8f1c1f | |||
| a7fee633b0 | |||
| 4d64be29ac | |||
| 7dedf04e26 | |||
| 9a9b4fc521 | |||
| debf9c820c | |||
| 51ea7e52b8 | |||
| aa1f34f157 | |||
| 670b2d845f | |||
| d8da36d05d | |||
| 0c2d47333f | |||
| 210cd2f93f | |||
| 2c997398b9 | |||
| 57a3936a6d | |||
| fa09e991f4 | |||
| 680f814e6b | |||
| afec1e681c | |||
| cf07a106b5 | |||
| a79b7ec812 | |||
| d8d1cb5665 | |||
| ac4b29d13c | |||
| 5ae7749554 | |||
| a9d5585ef9 | |||
| 224b28bc2c | |||
| 9fc48341f7 | |||
| 8b83b2f7e0 | |||
| 6bb818b9a1 | |||
| 1e8a75e8f7 | |||
| 6392272a2e | |||
| 51191c3eb6 | |||
| 2aa021f74b | |||
| 4ed8f01981 | |||
| 9f2eadbfad | |||
| 084d2f6c09 | |||
| 737bf822d1 | |||
| 00c7c4e205 | |||
| c4670e1cb1 | |||
| 4cfaf64de3 | |||
| 2eb5d30e91 | |||
| 0c6238e380 | |||
| 299ee0fd25 | |||
| 4b610eceba | |||
| 84cf398cc5 | |||
| 470a945bad | |||
| ea28f6024f | |||
| c1ddfbf21c | |||
| 490f9191ff | |||
| dbdd0ac4bf | |||
| a3b740a390 | |||
| cbcd693086 | |||
| 428cfb144e | |||
| e352ddeb99 | |||
| 1e4ae7b4c7 | |||
| cf48d2f3f1 | |||
| d462fda694 | |||
| 5089901491 | |||
| 71d9dfc724 | |||
| 92523eaf30 | |||
| 2be3aecf39 | |||
| d206eafd77 | |||
| e9f259bf6f | |||
| 897f2b5bd2 | |||
| 7b414ce5b5 | |||
| 35714779f9 | |||
| 1d926c2411 | |||
| 753b81d3dd | |||
| 6078322db5 | |||
| 4577aca730 | |||
| a53a527d80 | |||
| 67a6501ee0 | |||
| fce631cc87 | |||
| 2e4287d585 | |||
| 8624576bf8 | |||
| 4d72446c64 | |||
| faf621b646 | |||
| f2acea30e2 | |||
| c6dbe334e9 | |||
| a8c842b8d0 | |||
| 95e34f0b73 | |||
| 69fd1b915b | |||
| 7483455bb8 | |||
| 77e6f100b7 | |||
| 69a3a7d5ba | |||
| 6dae2e7068 | |||
| d3bd782165 | |||
| b49782c7b4 | |||
| 8ca8d51025 | |||
| f8affd2f29 | |||
| 3d4e2c3165 | |||
| 7cca06377c | |||
| 22f15f5cbf | |||
| b8c73b7730 | |||
| fb71498e6a | |||
| bc16d7f5ce | |||
| 62bc87e251 | |||
| b1f010ad60 | |||
| 8e4a3fc42b | |||
| bf08952fe0 | |||
| 51792a8eda | |||
| dd4fdd8de3 | |||
| b9f6abc9b3 | |||
| 4ca4d9fd82 | |||
| 1ed05b96b4 | |||
| e9b7cb505d | |||
| a7f1da85b1 | |||
| 59c0bfe3f5 | |||
| f893819f32 | |||
| 8c40e1cf05 | |||
| afb9cc2e44 | |||
| 8e854adaa3 | |||
| 860874ef5c | |||
| 4b263c1e7a | |||
| eaeae88f04 | |||
| 7a84c5aec3 | |||
| 1d91f9e78c | |||
| 1a8538b617 | |||
| ddd1d662b1 | |||
| c56319d1c2 | |||
| d489912705 | |||
| f95437ad1a | |||
| 04bbac1252 | |||
| f058f2bc71 | |||
| aa873f75f2 | |||
| 70de481b52 | |||
| b41e8bbb8c | |||
| 17f700c77d | |||
| 89b4a51774 | |||
| 92627b0a69 | |||
| 8e974a4fe3 | |||
| 60a151f798 | |||
| 6839539321 | |||
| 0dc5685aa9 | |||
| 35f8a1e539 | |||
| 3adbe4b74f | |||
| 179b9e16d6 | |||
| b573b380ab | |||
| fb9c92e941 | |||
| 2ae6af46ef | |||
| 224411b593 | |||
| e366f3545f | |||
| 3721eea772 | |||
| e16e06d8b6 | |||
| a60bd5eba7 | |||
| 8dca13f4e5 | |||
| d0464b988a | |||
| 60662c443d | |||
| 2420cfdd9a | |||
| 2aad0959c4 | |||
| 9a145cb0b0 | |||
| 690c2fe2f7 | |||
| 9a833023ce | |||
| 62456f632e | |||
| 4128de90fd | |||
| 0eb24ef7b1 | |||
| 1c96e82629 | |||
| 7af43de521 | |||
| 502af9c782 | |||
| 1897fe9467 | |||
| 1da4c35cdc | |||
| 9de48bf400 | |||
| ffb3f51fb6 | |||
| 7e2852b7e6 | |||
| 184db4c933 | |||
| 6beb2576ce | |||
| 7f3e43d929 | |||
| 4dcb71042c | |||
| 30c09006b0 | |||
| fbd9e1d979 | |||
| d143aee8c3 | |||
| aaadc5e450 | |||
| 861c389277 | |||
| c3b41b047d | |||
| 1499036792 | |||
| 903f9a08a2 | |||
| 1228bdd756 | |||
| 9e35325600 | |||
| 0ec9492838 | |||
| fc7fe48920 | |||
| e3d272404d | |||
| 9e4efae3a5 | |||
| df16946d37 | |||
| 61f4742d19 | |||
| cad081f1bc | |||
| bc8870a735 | |||
| 713227f0fe | |||
| 661aa92732 | |||
| f8f15c51b1 | |||
| 32805d9bf8 | |||
| 7767284c98 | |||
| 9efb9600c9 | |||
| dcb3f2879a | |||
| 6d12f251a1 | |||
| 8ae66e9a22 | |||
| db74c102f9 | |||
| d1eea10fe6 | |||
| 1b4009e4a1 | |||
| 983a5423c9 | |||
| 99934ec16c | |||
| 7d59a6074f | |||
| 70972cc92e | |||
| c81f6d659d | |||
| 6097b0e946 | |||
| 7c1865f36f | |||
| 3a0c165cec | |||
| e8a8d4ec19 | |||
| cd0982ce20 | |||
| b4aeb435c6 | |||
| c51f9f1dcb | |||
| 94d5ed0773 | |||
| baf1369f58 | |||
| 35b25289bd | |||
| e336b8324a | |||
| d0e6162386 | |||
| c5e4316e37 | |||
| b52c12a922 | |||
| 1127df66f7 | |||
| 4b98ceb2d4 | |||
| 5286b90b27 | |||
| 9ffa194d6d | |||
| b9c6bac15b | |||
| 1aa18177c9 | |||
| 068d852c20 | |||
| 5caf9560cb | |||
| e1194ff391 | |||
| 6fb3bece43 | |||
| 90ae7b0741 | |||
| 8a13c7b312 | |||
| 519673ec01 | |||
| b4a22b29f3 | |||
| 2bbddc81b1 | |||
| e0171c45e1 | |||
| 3427d8fd07 | |||
| 2b3bfdec99 | |||
| 2cca6d4bba | |||
| c776dc2206 | |||
| 34c971ad77 | |||
| 91b2932d62 | |||
| 3127cc6780 | |||
| 0dc5439726 | |||
| f7e3db9043 | |||
| 7a635430f1 | |||
| 0bb11bcca5 | |||
| 412d15a9b4 | |||
| 10eda384e6 | |||
| 7866572191 | |||
| 4b5cbb3fcc | |||
| 614b46e6fc | |||
| f7ad32c1cc | |||
| 2afb106ba9 | |||
| fce2cc8636 | |||
| ba4362ae64 | |||
| 8545938654 | |||
| 4d87b3aafb | |||
| 5784d3f5d1 | |||
| fb1b5280fe | |||
| 253e655b31 | |||
| 13eb5bb1e8 | |||
| 72dcd0a073 | |||
| 1971db2405 | |||
| 3093471063 | |||
| 14874d7869 | |||
| bfdec9e08b | |||
| cbec924300 | |||
| a03f87b907 | |||
| e100f475ae | |||
| 76e66bcf6f | |||
| 26a50b9a79 | |||
| 4e38760b44 | |||
| aef9805bdf | |||
| bb7ccde958 | |||
| 55aef95304 | |||
| aa338f7aa7 | |||
| ee06368a4e | |||
| edfb2006b2 | |||
| 015ecfd182 | |||
| 99b65bead6 | |||
| 98efa0f804 | |||
| 2ff7458c4e | |||
| 60fa35376b | |||
| 2fbaba710d | |||
| 222eafe86d | |||
| 86bb513db6 | |||
| 88d4c5101e | |||
| df4126514f | |||
| 471643d4ea | |||
| 444a48c8f2 | |||
| ec951dd87f | |||
| 7ac385b317 | |||
| 2f435d561e | |||
| 3a718d2cd8 | |||
| d87a420752 | |||
| 6491de3064 | |||
| 5cc01d7aab | |||
| 628cd98fd0 | |||
| 8ce65cbe3d | |||
| 0b2928eb3c | |||
| 45ce43ab15 | |||
| 637044e99d | |||
| 3a96e02fc7 | |||
| 18a3283cb4 | |||
| c3b8ba4d04 | |||
| a51ccf6d67 | |||
| 6b1224f9f8 | |||
| 907f9c00fb | |||
| ed72062d33 | |||
| af9f5cef2f | |||
| 2c205921d6 | |||
| 4e2b424eeb | |||
| 22e738a164 | |||
| 6824cdd4a1 | |||
| d44dd05462 | |||
| 7d6666c0b6 | |||
| 070882655d | |||
| cdb0831174 | |||
| 0a864aea41 | |||
| a4464610eb | |||
| 18a03d57b4 | |||
| 49bbc0754a | |||
| f21b061443 | |||
| 34446fae37 | |||
| d1aeb621d4 | |||
| 2f81e087e7 | |||
| fe96afc8c3 | |||
| 9236993c22 | |||
| ad2d62d13b | |||
| 8f5833ca15 | |||
| 1adf8c890f | |||
| 416c2a56fb | |||
| 1140e43d67 | |||
| ab4faa36da | |||
| 38ca9815ba | |||
| 125965dabd | |||
| ce4369976c | |||
| dc340fc766 | |||
| c28e3ec5c2 | |||
| 6fe7cf18bd | |||
| 5af9577d31 | |||
| c03c37420d | |||
| b818c0feac | |||
| bcb3003784 | |||
| ecd033ffd9 | |||
| abadeef7d2 | |||
| 427212ed13 | |||
| 73435497a2 | |||
| 067cb0f21d | |||
| a087a535d2 | |||
| 28745e81f8 | |||
| 29c74bbbe1 | |||
| 35a7b13cde | |||
| 62b77cc9fa | |||
| 782314d5bd | |||
| 33b5e02f23 | |||
| ec518c2ece | |||
| b7b2dff26b | |||
| 456ffb8586 | |||
| 1d09965d9a | |||
| 82cff8f66e | |||
| b97acc7862 | |||
| 0adc963129 | |||
| 4be1387c62 | |||
| f4e17f0de7 | |||
| a03e1aa1f8 | |||
| 7fcfbb2a54 | |||
| 59a4761424 | |||
| 6ceae331ce | |||
| 2a1f0175a9 | |||
| e5fca5f545 | |||
| 3f8ebc4a19 | |||
| ff99f55b0a | |||
| d049658cdc | |||
| e8103b0dbe | |||
| 537ea1fd83 | |||
| fd66b2b351 | |||
| ab6ffb74b7 | |||
| 20fb2b3083 | |||
| e9877bb52b | |||
| 7debf34fb0 | |||
| 41afaf4f66 | |||
| ee19c214e7 | |||
| 534ca2b042 | |||
| ac5cbd2d22 | |||
| 17372cd0a4 | |||
| 0bbeb481f4 | |||
| 61c9709974 | |||
| d0364e3734 | |||
| 5b5852e336 | |||
| 883aeebc8f | |||
| c16e0edaf0 | |||
| 98bb6eaf2a | |||
| 36d8b96b61 | |||
| 3bb3d6a6cd | |||
| 9971a86d5c | |||
| 71ab2c8dea | |||
| 80561c8784 | |||
| 4be0b1b3b6 | |||
| 48a32e8f6d | |||
| 9e6fe50aed | |||
| fa4edcba93 | |||
| b863e3892e | |||
| 0a57a7b7ee | |||
| e249b62d13 | |||
| 5c015f275b | |||
| 6faddc15e9 | |||
| 894cd21032 | |||
| 7a1b56e202 | |||
| 19911eb8c8 | |||
| a307917b66 | |||
| cb5f45acc7 | |||
| 0dbf9c907d | |||
| 9fc8234eaf | |||
| 61840de99f | |||
| 30da6e10a1 | |||
| 4650edbd17 | |||
| 7646e3d9a1 | |||
| c5198b198a | |||
| 87b70a3faf | |||
| 1ef0c7bd14 | |||
| 02ee5b8c7e | |||
| 5a2b509d68 | |||
| 11cbcd45bb | |||
| e82d5be71d | |||
| 065d5ade3f | |||
| 464015bed6 | |||
| 190323cf05 | |||
| 94654c753b | |||
| 8a589982d1 | |||
| bd9325c66d | |||
| 1bdd4720bb | |||
| 55aec4cc8b | |||
| 5ec294207d | |||
| 3b45a339d3 | |||
| 0742c2be33 | |||
| e8730d6d18 | |||
| 2bc3b72e15 | |||
| 56124fd90d | |||
| 81ccba83c3 | |||
| 5f64a3ae33 | |||
| a80b1aed08 | |||
| 3724045e37 | |||
| a368af0598 | |||
| 8128a7acac | |||
| 51910e6740 | |||
| 8a2be8195a | |||
| ec2c418c4d | |||
| b89b9e4ebd | |||
| de008edc68 | |||
| 129c7a15c7 | |||
| 29325aa341 | |||
| a864e67a6c | |||
| 190aff51b6 | |||
| ab398032df | |||
| 8f1d127e96 | |||
| 01ea108ab9 | |||
| 2b10162d5e | |||
| 4b1e1a22db | |||
| f500adb158 | |||
| 5ec7e680e6 | |||
| 7277389c9a | |||
| 4c7a2737d0 | |||
| cd4594f3ae | |||
| 4733b3c919 | |||
| aa78945ed8 | |||
| 7ac8beb161 | |||
| 0f30d85ae8 | |||
| f8a976290c | |||
| cd73e76712 | |||
| 4616470493 | |||
| ac6afad280 | |||
| dde76789a1 | |||
| b8c4482dad | |||
| fb90189f3c | |||
| ac56f9f7a6 | |||
| 9fd9979ce3 | |||
| a1ae7ca119 | |||
| 08f29b7067 | |||
| 503367b8e2 | |||
| 36aac1d8c8 | |||
| 42229fe6bc | |||
| 0ae824ec35 | |||
| 0a1d8245ee | |||
| 95c2288890 | |||
| 7ac97ca739 | |||
| 14548e4bc5 | |||
| 72c5e7c35a | |||
| 4fbc4fccdc | |||
| a4de291155 | |||
| 67ea0a5c24 | |||
| d5eeab16b6 | |||
| 0be2248f41 | |||
| 7b7f968ae2 | |||
| ca1e1abeb8 | |||
| 00451ae78f | |||
| 04e9fc7315 | |||
| 6dbef3f743 | |||
| 42cc086bd0 | |||
| 4c40b04c8e | |||
| 6126ba8678 | |||
| 51cc7692cb | |||
| 414f8c64c0 | |||
| 60a735b726 | |||
| d6f6642271 | |||
| be3880a05d | |||
| f40f1a4304 | |||
| d8bb1d649c | |||
| 1bf43cf137 | |||
| 86fa87bb7a | |||
| 5c89b5a7a2 | |||
| 9a8b4cacff | |||
| da6a54aa74 | |||
| 478d65a649 | |||
| abd391266a | |||
| f282e0e69b | |||
| f283cbbcca | |||
| 89a392b749 | |||
| 2a3e016dc2 | |||
| 5d56177bc7 | |||
| c043dbf8e5 | |||
| af0ec4e3da | |||
| 618bc05a8b | |||
| b86f0a874f | |||
| c473c72b84 | |||
| 87405dc9ea | |||
| b01993fe28 | |||
| 24c7cd0c8d | |||
| 2739f84fea | |||
| 98304b8816 | |||
| 9a29cef69a | |||
| ff9738f5cc | |||
| a657908c20 | |||
| f59128f25d | |||
| c08a617c65 | |||
| 5017fce70c | |||
| 304cd9ddfc | |||
| e7eec6e160 | |||
| 3dd44c97e9 | |||
| bc00f2d577 | |||
| d80bfe61df | |||
| de3ac01b98 | |||
| 5c488fe4ea | |||
| 1d7874fad6 | |||
| c4f337f2ae | |||
| 09d70866e3 | |||
| 5052720efb | |||
| 73f53a913c | |||
| 13ce234ac8 | |||
| a38ee5e16c | |||
| 0a30ea157b | |||
| 658f0cbea5 | |||
| 51378e922f | |||
| 295aee494e | |||
| 72350f2fae | |||
| 49bbadd721 | |||
| 220e062d1e | |||
| b0ac98a5a9 | |||
| 84decf1310 | |||
| 7d66f2628a | |||
| 285e0c7a81 | |||
| e0153d6c11 | |||
| c241a0ba8f | |||
| ca7c7efc9c | |||
| 6a29b9492c | |||
| 599040d64a | |||
| ec6c2303be | |||
| f4419d2357 | |||
| c75df95cce | |||
| b2e8c75572 | |||
| 6bf3737c24 | |||
| 075c1f234a | |||
| fd21273251 | |||
| bb822801fb | |||
| 8d1fc7faa5 | |||
| 94728e7b6a | |||
| e031f80b2e | |||
| 6111dc69ad | |||
| 49188b291b | |||
| ba8632aa22 | |||
| 726bbe4f6f | |||
| f1e1d94380 | |||
| 607970a1ed | |||
| aef65d9d05 | |||
| 4d26fe1edd | |||
| e0bc987c25 | |||
| a07c5c8d9d | |||
| 777256bf30 | |||
| 60de3b3250 | |||
| 074919b70c | |||
| f90e9b4eba | |||
| 6789b1c327 | |||
| f46fe0ec4c | |||
| f88aea2d32 | |||
| 29f534501c | |||
| 7b18e06006 | |||
| 50567665ab | |||
| 2135205f40 | |||
| 51241108fa | |||
| 05c5b2968d | |||
| 00e6d1aa7a | |||
| eb4fb872a3 | |||
| 9e7825aee8 | |||
| f8b75c7237 | |||
| 843b2f32e0 | |||
| 4db7154130 | |||
| c7190a0d07 | |||
| 8bd273c48a | |||
| 9f0e9a5525 | |||
| 1486c56249 | |||
| 53d895f046 | |||
| d6e0ea049a | |||
| 58f192b6e9 | |||
| dba4bae830 | |||
| 8df6362ac0 | |||
| 2a84bdcf9f | |||
| 158f94f069 | |||
| 3fde0fa7a3 | |||
| 1ac9a0e154 | |||
| 8c816edab4 | |||
| 858b8a25bb | |||
| 9dfac9c5f5 | |||
| 0a828e843d | |||
| 16414f6a4a | |||
| 4e902e891e | |||
| 5cff8b3bed | |||
| b5671d14a4 | |||
| be202fd53b | |||
| 601663b658 | |||
| cb63931f30 | |||
| 651a1de601 | |||
| 841adc05cd | |||
| e78f1b804f | |||
| 8d94a7e872 | |||
| ce53bb2d47 | |||
| 09a74768d5 | |||
| e4f89f7084 | |||
| a6df9e1f04 | |||
| 0e885d21f9 | |||
| 387eccb44a | |||
| 7086073227 | |||
| 9006a9719e | |||
| 9e554ca0af | |||
| 6899084f06 | |||
| 4396902982 | |||
| 73dc9c3439 | |||
| 3c59985734 | |||
| 13a23a9753 | |||
| 3c543d686a | |||
| 2bd0bb68a1 | |||
| 9d9dbd0048 | |||
| a1dcdd49fb | |||
| 880e97a922 | |||
| 9ba1537f18 | |||
| c0d167a2b0 | |||
| 3931f9c679 | |||
| 3e4d2b069a | |||
| d679f69386 | |||
| 193276a0b2 | |||
| 1e308eea98 | |||
| cc9ffdd182 | |||
| 1e4a8afc11 | |||
| b6dee4671a | |||
| 98756d4c7f | |||
| 16c6535baf | |||
| fe2c6dafcd | |||
| 1c531fb659 | |||
| 2ece064216 | |||
| fe74e18ea3 | |||
| fa714f7176 | |||
| 7a057bda13 | |||
| 7348828cc3 | |||
| 74ef7b58fb | |||
| 3fc784ec9d | |||
| 52292aefed | |||
| ba0088ed73 | |||
| 452e64c919 | |||
| ad7df9a6ef | |||
| abb55346f9 | |||
| b16ed52378 | |||
| be8d347b0c | |||
| 0146794b74 | |||
| 996319a9cb | |||
| a68b978044 | |||
| 556f8f1db0 | |||
| bbee4b42ae | |||
| 935227f104 | |||
| 9e540a2128 | |||
| 3c7dbd6b0b | |||
| 4809299f86 | |||
| 41ad5f5109 | |||
| 104bd61576 | |||
| e833233982 | |||
| 8f83dc5fd4 | |||
| 9f9d45e59c | |||
| bb27293aba | |||
| 445b1277e9 | |||
| 3016f563bf | |||
| d1b9c29f0f | |||
| 255c03a4a3 | |||
| b49c377b5f | |||
| 45fcda443c | |||
| 73daecf9b7 | |||
| e6f5e06ca5 | |||
| 9677d1dba9 | |||
| 134004e787 | |||
| 6ea6d5e220 | |||
| 003758e8cc | |||
| 3054a4ee49 | |||
| 21aeadcc56 | |||
| d5b4374c0d | |||
| 39f6e449ed | |||
| a87cbedd52 | |||
| 90649ce00b | |||
| de3eb7f259 | |||
| 5298475de2 | |||
| 5f4c573282 | |||
| 16084dabfe | |||
| 7c46243641 | |||
| 96f1913592 | |||
| b92cad8d91 | |||
| f968dbe926 | |||
| c693a78cc1 | |||
| 4aa1b6070b | |||
| 08724b1d53 | |||
| 6c39c8c73b | |||
| c3d35ac282 | |||
| 37c6a562cd | |||
| 98ab569665 | |||
| 318d4194c3 | |||
| 4b50b27e14 | |||
| af02d1727c | |||
| d2a39999be | |||
| 3207a06e3f | |||
| 98457883cc | |||
| 8e4ce11053 | |||
| 6ae5b4992f | |||
| 3c624d5039 | |||
| ddd2efe441 | |||
| 7e419cb2de | |||
| 0093c7cfb7 | |||
| 65c45d4c28 | |||
| 98fc9ea703 | |||
| dbeb08e5e1 | |||
| a48523c94b | |||
| 1335fffdaa | |||
| 78f96dbcf9 | |||
| 86afeea532 | |||
| 66559785ea | |||
| f016fc4d06 | |||
| e0bddbc84a | |||
| 3639dc93ef | |||
| e1da4bd1f0 | |||
| 99ef9118ee | |||
| 68b1c0eabb | |||
| d73ca273e0 | |||
| 9badf0e3e3 | |||
| c720edcf41 | |||
| 483c6a2758 | |||
| 6168f4d758 | |||
| 687730be7b | |||
| cddb2bc2cf | |||
| fa7c9b4c47 | |||
| 1fd429f582 | |||
| 6d61c036f9 | |||
| f82f4b0d7b | |||
| fff39caa71 | |||
| 5968d64e3d | |||
| 202c53375b | |||
| 9eea853724 | |||
| 9526fa294a | |||
| e025fa2995 | |||
| 5c15af2b9c | |||
| 3e094c7bf4 | |||
| 45a4b2b97d | |||
| 1d62a6613d | |||
| 28f182a291 | |||
| f78ba3ad1e | |||
| e2ff036965 | |||
| 7c7071a3a9 | |||
| e90809c010 | |||
| 2426fcd7d6 | |||
| f433c28e75 | |||
| cac8755305 | |||
| a283dc0c62 | |||
| 99002fa1cf | |||
| 9060c87d60 | |||
| 1d275e3313 | |||
| 77da2a7fd2 | |||
| b9cc53b694 | |||
| 62d47077b1 | |||
| 3269e79ff3 | |||
| 307faed57b | |||
| dc6a9a53f0 | |||
| 285119852b | |||
| 65fa6517d3 | |||
| 2c27074b04 | |||
| 66e4be0b57 | |||
| 14d94e33bb | |||
| 541f50aecc | |||
| 95d87f7183 | |||
| 6738960e46 | |||
| ab60eaac95 | |||
| 76f009c546 | |||
| b9c61f8b94 | |||
| 125be3f2bf | |||
| 9df44eeb87 | |||
| 97aa6ee0d6 | |||
| 80c9bca75b | |||
| 9e253086ef | |||
| 6aeb5299dc | |||
| 888eabe154 | |||
| 29d5c1bdd7 | |||
| eee33cda04 | |||
| df4f65d36d | |||
| 8e9335454e | |||
| 4e4b0f2f51 | |||
| c28d6c85af | |||
| 3cdbac76a5 | |||
| 20bcc662c8 | |||
| 908accd290 | |||
| a621c5fc72 | |||
| 92f0ac1578 | |||
| 8b3667ec9b | |||
| 875fa0936b | |||
| 669844f055 | |||
| 9785b930da | |||
| f3195d257e | |||
| 349162852d | |||
| 69f330737f | |||
| 7d10fbaa25 | |||
| 9d964b572b | |||
| a0978269f8 | |||
| 9a6196bbd7 | |||
| 812f589762 | |||
| b9e676d926 | |||
| 49e6e54c31 | |||
| 4d398fd4d5 | |||
| b74f4adfd8 | |||
| be5abccd81 | |||
| 1792489cd0 | |||
| 4284fd8fc6 | |||
| 036106db82 | |||
| c6ed032659 | |||
| b521204c99 | |||
| b38502e0bd | |||
| 2fe03bd4b3 | |||
| 68a0f616dc | |||
| 1edce4295e | |||
| ca386c343e | |||
| efb0b6526a | |||
| 4a406ad261 | |||
| fb701cc20d | |||
| 0b0478c9ba | |||
| 79ff630c41 | |||
| 4679440520 | |||
| b38f77acea | |||
| 896330d0ec | |||
| 205803a800 | |||
| e6187f099a | |||
| c148a5c797 | |||
| 3d730547af | |||
| c3f1b12b7e | |||
| 9f9a347272 | |||
| d63869ca15 | |||
| d33a243020 | |||
| 558e8fad61 | |||
| b0f094a9d9 | |||
| 84e1528a1d | |||
| 2a1372926f | |||
| 73b2fea954 | |||
| 8263f1ec85 | |||
| 436ab32937 | |||
| cb904e5386 | |||
| 486bf0edcd | |||
| 06e5f2878f | |||
| 5d1a7a7ca4 | |||
| 197fe3c15b | |||
| 09ac4cf970 | |||
| f64a20fbac | |||
| 86b566a8cd | |||
| 2e7ecc93c8 | |||
| c7f79d4b7b | |||
| c452f092cc | |||
| 0764b4bfd4 | |||
| cd9552c9e0 | |||
| 508bfb7891 | |||
| 998b5af4ae | |||
| 5b1eac58e4 | |||
| 9e0d25b77e | |||
| 62488211f7 | |||
| 0b4b183ba7 | |||
| f3532f6128 | |||
| a5e792d3ba | |||
| 19fc1b6958 | |||
| 2702b71cc6 | |||
| 97b3ef0f03 | |||
| 3431805088 | |||
| 306da2fcc5 | |||
| a7f2a91e6a | |||
| dcd85d0e9a | |||
| 82a3476f62 | |||
| cf32ae9a00 | |||
| 561c001b08 | |||
| 26ff021887 | |||
| eb461b5bfa | |||
| 42114a7f0d | |||
| 10b08ce42c | |||
| c0934136d0 | |||
| fb77d33cb7 | |||
| 7ebbd8c0bf | |||
| 6c16251876 | |||
| dc18ce41f5 | |||
| 2669e381b1 | |||
| 3deb9cbb35 | |||
| eeaef841c8 | |||
| 7f52cc95ec | |||
| 71e9c464b9 | |||
| 84fd74de4a | |||
| f0661e6d19 | |||
| 6f03c8f969 | |||
| d38cff4def | |||
| e45468cf3c | |||
| ed843b7285 | |||
| 117dd82617 | |||
| b043db8237 | |||
| ee703df2f3 | |||
| 8b7cf85f23 | |||
| c44bd22108 | |||
| 5c245c70f7 | |||
| debdd18a58 | |||
| 29504e8d62 | |||
| a0edee3c45 | |||
| 7f457d9ae8 | |||
| dd5ac2d87b | |||
| 76172d49c3 | |||
| 7b6fc8e03e | |||
| 0be7620209 | |||
| 3b6a38dd90 | |||
| 0b001913de | |||
| b293b5b30e | |||
| 9f7f6ab157 | |||
| b5c88a59bf | |||
| 040c10596e | |||
| 1f1c7c0668 | |||
| ad1561f090 | |||
| 83306364ce | |||
| 8ba0d49fb3 | |||
| e1c7c16d91 | |||
| 01cca463bb | |||
| 44aa842f20 | |||
| 2d94bbaef7 | |||
| b2bc656e15 | |||
| 21b2f554df | |||
| cd4795d50a | |||
| 583141a374 | |||
| 816cc11a19 | |||
| d27729d110 | |||
| f4a37539a0 | |||
| cec2100915 | |||
| cab4629c4b | |||
| 6f3c389e22 | |||
| 729e2882b5 | |||
| b786ee4a36 | |||
| 7569ca1fd4 | |||
| 93925c1bfb | |||
| 658179d9c3 | |||
| bc74168e36 | |||
| 22afea72f5 | |||
| a2d42af747 | |||
| b04d06da4c | |||
| f1052ae2f3 | |||
| c660aadf90 | |||
| 0fd8abe0ff | |||
| fcb3183e71 | |||
| 3f2ed774ad | |||
| 6c90612fd2 | |||
| ffb10a588e | |||
| 43069c396d | |||
| 974c5cf8a2 | |||
| c434d7c954 | |||
| 518846a281 | |||
| ec316f589b | |||
| 92ea8e4308 | |||
| 0a6fc1e020 | |||
| d06cf41581 | |||
| c94c746120 | |||
| 324c8a2b98 | |||
| 0a2b191711 | |||
| 334db692b2 | |||
| 458176a0af | |||
| 954cf5af1f | |||
| d46158b381 | |||
| f883525c27 | |||
| 453412ad4d | |||
| 5f80f8e3af | |||
| 61c308a0e3 | |||
| 96a7394aaf | |||
| 429e153cd4 | |||
| d856554aa4 | |||
| 227552f54b | |||
| 32ed7faff2 | |||
| 35481c1f59 | |||
| f6510bd667 | |||
| b51eb92256 | |||
| 2b48c779ef | |||
| 9783f77110 | |||
| e764fd07b9 | |||
| 5aa8e88381 | |||
| 2775aa023b | |||
| 096adebb48 | |||
| 1ba08ec630 | |||
| d72337b770 | |||
| c053aa6299 | |||
| 5d70b98cd4 | |||
| ea0812da97 | |||
| a771eda369 | |||
| be40015526 | |||
| c56f6ff1fb | |||
| 6dab6c327c | |||
| 4f4e4e7b1d | |||
| 259a1b9566 | |||
| 43533b5f3a | |||
| 51f7628564 | |||
| 2aa2591f0f | |||
| 6301b8f2fe | |||
| dbc29b86d8 | |||
| 5e1e088edd | |||
| 1c74f3d2ce | |||
| 583918d617 | |||
| a15e1fbca0 | |||
| 8699a7e97c | |||
| a3869c3be0 | |||
| f777103030 | |||
| 09eabbf773 | |||
| 64932c97a4 | |||
| 00e3710d66 | |||
| bd99164416 | |||
| e37cee3be7 | |||
| 7c33abdbe6 | |||
| e6bb9a972b | |||
| e40773e436 | |||
| 3c4c8b8fe6 | |||
| 5aaa32b788 | |||
| e6f53ce2e7 | |||
| f18d7c484b | |||
| fefc0fec76 | |||
| 2949c65c79 | |||
| 19dcc1a914 | |||
| 64cdbb37f5 | |||
| c31f8865c5 | |||
| 9698ba0f5d | |||
| 836f15f5d7 | |||
| 0d863b781f | |||
| c016524079 | |||
| d85773c2da | |||
| 00c123fd1e | |||
| 48a8811026 | |||
| ab01f59eae | |||
| 2bdd24180c | |||
| 126d1e4c58 | |||
| 582072e174 | |||
| fbbea88502 | |||
| 100841d6f7 | |||
| cc096a548f | |||
| f6c9680551 | |||
| 1f7951bf9f | |||
| a96e465b13 | |||
| bf6c955cd0 | |||
| 854578d300 | |||
| beb0a2007e | |||
| e0ab35920e | |||
| 75cff407bf | |||
| 297b20857c | |||
| 04ccc272e1 | |||
| 22f6267a50 | |||
| 8fed0c2b1f | |||
| 154527947d | |||
| a8b29b388b | |||
| df845a0db4 | |||
| 43b769e3e8 | |||
| ea58b708f8 | |||
| 917786f8b8 | |||
| 8402209afd | |||
| 5c0b661189 | |||
| 47f7d78258 | |||
| c50becc085 | |||
| 0771b831e7 | |||
| b47e1c22c5 | |||
| 166d223577 | |||
| 310f983075 | |||
| ac0f472ac6 | |||
| 977163588f | |||
| da0b6e557e | |||
| 411a74b2aa | |||
| 0380353ff4 | |||
| c0590ddc0f | |||
| 020aba1c90 | |||
| d46c5a10fc | |||
| 1e24758b21 | |||
| 308b41a1ec | |||
| e07e6b0f0f | |||
| 35ec6677ba | |||
| 2110788f43 | |||
| d61e250e0b | |||
| 3d3c0377ff | |||
| 1c8135388f | |||
| e47ccfc58d | |||
| 768cc5e10b | |||
| 2be5cf5ee8 | |||
| 0ac1e42658 | |||
| 131851e650 | |||
| 56bb0b26ca | |||
| 3d8a1eb8ae | |||
| a930dda037 | |||
| 97154f97f1 | |||
| 626077d625 | |||
| f45b83e947 | |||
| a2ea577fd8 | |||
| 3a69a179bc | |||
| 703d33b69c | |||
| ef5b68cb9b | |||
| bf6017c60c | |||
| 097a588fc1 | |||
| cac2cb2bac | |||
| ef571ba096 | |||
| 419702a1ee | |||
| 2c249f5167 | |||
| b90660f46c | |||
| 578999e705 | |||
| e2d6e9d812 | |||
| 250937bd51 | |||
| d85532ca85 | |||
| 862c762eaa | |||
| 8e6c4ec683 | |||
| d82fe39a17 | |||
| ca43a5ff43 | |||
| 449254581f | |||
| 7a8557590c | |||
| be6ecaa380 | |||
| 54a29b39bf | |||
| a000ce2ec5 | |||
| 44436ab29d | |||
| 4863b8c9a3 | |||
| 1662faafd2 | |||
| 693b76ee18 | |||
| 7da4235fc8 | |||
| 66b5315a13 | |||
| 5dd04479c2 | |||
| 469a612e41 | |||
| 7c8d175c9c |
@@ -56,7 +56,7 @@ body:
|
||||
label: Environment
|
||||
description: Share your environment details. Reports without proper environment details will likely be closed.
|
||||
value: |
|
||||
- APINTO Dashboard version:
|
||||
- ApiPark version:
|
||||
- Operating system (run `uname -a`):
|
||||
validations:
|
||||
required: true
|
||||
@@ -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:
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
/.vscode/
|
||||
.air.toml
|
||||
/tmp/
|
||||
/work
|
||||
/work
|
||||
/cmd/
|
||||
|
||||
@@ -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
|
||||
@@ -1,7 +1,7 @@
|
||||
provider: bailian
|
||||
label:
|
||||
zh_Hans: 阿里云百炼
|
||||
en_US: bailian
|
||||
en_US: BaiLian
|
||||
icon_small:
|
||||
en_US: icon_s_en.svg
|
||||
icon_large:
|
||||
|
||||
@@ -89,7 +89,7 @@ provider_credential_schema:
|
||||
zh_Hans: 为了进行验证,请输入一个您可用的模型名称 (例如:amazon.titan-text-lite-v1)
|
||||
model_config:
|
||||
access_configuration_status: true
|
||||
access_configuration_demo: "{}"
|
||||
access_configuration_demo: "{\"region\":\"\",\"model\":\"\"}"
|
||||
address: https://bedrock-runtime.amazonaws.com
|
||||
sort: 4
|
||||
recommend: true
|
||||
@@ -1,11 +1 @@
|
||||
<svg width="80" height="22" viewBox="0 0 80 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Group">
|
||||
<path id="Vector" d="M25.1152 10.5768C25.1152 14.1739 27.4253 16.6819 30.6264 16.6819C33.8274 16.6819 36.1375 14.1739 36.1375 10.5768C36.1375 6.97973 33.8274 4.47168 30.6264 4.47168C27.4253 4.47168 25.1152 6.97973 25.1152 10.5768ZM34.0254 10.5768C34.0254 13.1509 32.6229 14.8174 30.6264 14.8174C28.6298 14.8174 27.2273 13.1509 27.2273 10.5768C27.2273 8.00275 28.6298 6.33622 30.6264 6.33622C32.6229 6.33622 34.0254 8.00275 34.0254 10.5768Z" fill="black"/>
|
||||
<path id="Vector_2" d="M42.0868 16.682C44.5124 16.682 45.8984 14.636 45.8984 12.1774C45.8984 9.71889 44.5124 7.67285 42.0868 7.67285C40.9648 7.67285 40.1398 8.11836 39.5953 8.76188V7.83786H37.6152V19.4706H39.5953V15.593C40.1398 16.2365 40.9648 16.682 42.0868 16.682ZM39.5458 11.9299C39.5458 10.2964 40.4698 9.40539 41.6908 9.40539C43.1264 9.40539 43.9019 10.5274 43.9019 12.1774C43.9019 13.8275 43.1264 14.9495 41.6908 14.9495C40.4698 14.9495 39.5458 14.042 39.5458 12.4415V11.9299Z" fill="black"/>
|
||||
<path id="Vector_3" d="M51.2545 16.682C52.987 16.682 54.3565 15.7745 54.967 14.2565L53.2675 13.613C53.0035 14.504 52.228 14.999 51.2545 14.999C49.9839 14.999 49.0929 14.0915 48.9444 12.6065H55.0165V11.9464C55.0165 9.57039 53.68 7.67285 51.172 7.67285C48.6639 7.67285 47.0469 9.63639 47.0469 12.1774C47.0469 14.8505 48.7794 16.682 51.2545 16.682ZM51.1555 9.33939C52.4095 9.33939 53.0035 10.1644 53.02 11.1214H49.0434C49.3404 9.9499 50.1324 9.33939 51.1555 9.33939Z" fill="black"/>
|
||||
<path id="Vector_4" d="M56.5038 16.5005H58.4838V11.4184C58.4838 10.1809 59.3913 9.52089 60.2824 9.52089C61.3714 9.52089 61.8004 10.2964 61.8004 11.3689V16.5005H63.7804V10.7914C63.7804 8.92688 62.6914 7.67285 60.8764 7.67285C59.7544 7.67285 58.9788 8.18436 58.4838 8.76188V7.83786H56.5038V16.5005Z" fill="black"/>
|
||||
<path id="Vector_5" d="M69.5799 4.65332L65.0918 16.5006H67.1873L68.1939 13.7945H73.309L74.332 16.5006H76.4605L71.9724 4.65332H69.5799ZM70.7349 6.99637L72.616 11.9465H68.8869L70.7349 6.99637Z" fill="black"/>
|
||||
<path id="Vector_6" d="M79.8581 4.6875H77.7461V16.5348H79.8581V4.6875Z" fill="black"/>
|
||||
<path id="Vector_7" d="M20.2769 9.00448C20.776 7.50639 20.6041 5.86529 19.8059 4.50264C18.6055 2.41259 16.1924 1.33732 13.8356 1.84333C12.7871 0.662179 11.2808 -0.00952316 9.70154 0.000102043C7.29248 -0.00539807 5.155 1.54563 4.41386 3.83781C2.86626 4.15475 1.53042 5.12346 0.748717 6.49643C-0.460621 8.58097 -0.184928 11.2087 1.43073 12.9962C0.931596 14.4943 1.10348 16.1354 1.90168 17.498C3.10208 19.5881 5.51526 20.6634 7.87206 20.1573C8.91983 21.3385 10.4269 22.0102 12.0061 21.9999C14.4165 22.0061 16.5547 20.4537 17.2958 18.1594C18.8434 17.8425 20.1793 16.8738 20.961 15.5008C22.1689 13.4163 21.8925 10.7906 20.2776 9.00311L20.2769 9.00448ZM12.0075 20.5623C11.0429 20.5637 10.1085 20.2261 9.36809 19.608C9.40178 19.5901 9.46022 19.5578 9.49803 19.5345L13.8789 17.0044C14.103 16.8772 14.2405 16.6386 14.2391 16.3808V10.2049L16.0906 11.274C16.1105 11.2836 16.1236 11.3028 16.1264 11.3248V16.4393C16.1236 18.7136 14.2818 20.5575 12.0075 20.5623ZM3.14952 16.7789C2.6662 15.9443 2.49225 14.9659 2.65795 14.0165C2.69026 14.0357 2.74732 14.0708 2.78789 14.0942L7.16873 16.6242C7.3908 16.7541 7.6658 16.7541 7.88856 16.6242L13.2367 13.5359V15.6741C13.2381 15.6961 13.2278 15.7174 13.2106 15.7311L8.78233 18.288C6.80985 19.4238 4.29079 18.7486 3.15021 16.7789H3.14952ZM1.99656 7.21626C2.47782 6.38024 3.23752 5.74085 4.14229 5.40878C4.14229 5.44659 4.14023 5.51328 4.14023 5.56003V10.6208C4.13885 10.878 4.27636 11.1165 4.4998 11.2437L9.84798 14.3313L7.9965 15.4004C7.97794 15.4128 7.95456 15.4149 7.93393 15.4059L3.50496 12.847C1.53661 11.7071 0.86147 9.18874 1.99587 7.21694L1.99656 7.21626ZM17.2085 10.7563L11.8603 7.66795L13.7118 6.59956C13.7304 6.58718 13.7537 6.58512 13.7744 6.59406L18.2033 9.15092C20.1751 10.2901 20.851 12.8126 19.7118 14.7844C19.2298 15.6191 18.4708 16.2584 17.5667 16.5912V11.3792C17.5688 11.122 17.432 10.8841 17.2092 10.7563H17.2085ZM19.0511 7.98284C19.0187 7.9629 18.9617 7.92852 18.9211 7.90515L14.5403 5.37509C14.3182 5.24515 14.0432 5.24515 13.8204 5.37509L8.47226 8.46341V6.32524C8.47088 6.30324 8.4812 6.28192 8.49838 6.26817L12.9267 3.71337C14.8991 2.57553 17.4209 3.25273 18.5581 5.2259C19.0387 6.05917 19.2126 7.03475 19.0497 7.98284H19.0511ZM7.46574 11.7937L5.61357 10.7246C5.59363 10.715 5.58057 10.6958 5.57782 10.6738V5.55935C5.5792 3.2823 7.42655 1.43701 9.7036 1.43838C10.6668 1.43838 11.5991 1.77664 12.3395 2.39265C12.3058 2.41053 12.2481 2.44284 12.2096 2.46622L7.82874 4.99627C7.60461 5.12346 7.46711 5.36134 7.46849 5.61916L7.46574 11.7924V11.7937ZM8.47157 9.62531L10.8538 8.24959L13.236 9.62462V12.3754L10.8538 13.7504L8.47157 12.3754V9.62531Z" fill="black"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="56" viewBox="0 0 24 24" width="56" xmlns="http://www.w3.org/2000/svg" style="flex: 0 0 auto; line-height: 1;"><title>LM Studio</title><path d="M2.84 2a1.273 1.273 0 100 2.547h14.107a1.273 1.273 0 100-2.547H2.84zM7.935 5.33a1.273 1.273 0 000 2.548H22.04a1.274 1.274 0 000-2.547H7.935zM3.624 9.935c0-.704.57-1.274 1.274-1.274h14.106a1.274 1.274 0 010 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM1.273 12.188a1.273 1.273 0 100 2.547H15.38a1.274 1.274 0 000-2.547H1.273zM3.624 16.792c0-.704.57-1.274 1.274-1.274h14.106a1.273 1.273 0 110 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM13.029 18.849a1.273 1.273 0 100 2.547h9.698a1.273 1.273 0 100-2.547h-9.698z" fill-opacity=".3"></path><path d="M2.84 2a1.273 1.273 0 100 2.547h10.287a1.274 1.274 0 000-2.547H2.84zM7.935 5.33a1.273 1.273 0 000 2.548H18.22a1.274 1.274 0 000-2.547H7.935zM3.624 9.935c0-.704.57-1.274 1.274-1.274h10.286a1.273 1.273 0 010 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM1.273 12.188a1.273 1.273 0 100 2.547H11.56a1.274 1.274 0 000-2.547H1.273zM3.624 16.792c0-.704.57-1.274 1.274-1.274h10.286a1.273 1.273 0 110 2.547H4.898c-.703 0-1.274-.57-1.274-1.273zM13.029 18.849a1.273 1.273 0 100 2.547h5.78a1.273 1.273 0 100-2.547h-5.78z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 17 KiB |
@@ -29,12 +29,12 @@ provider_credential_schema:
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的 API Key
|
||||
en_US: Enter your API Key
|
||||
- variable: base_url
|
||||
- variable: dashscope_api_base
|
||||
label:
|
||||
en_US: https://api.baichuan-ai.com/v1
|
||||
en_US: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
type: text-input
|
||||
required: false
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的 Base URL
|
||||
en_US: Enter your Base URL
|
||||
address: https://api.baichuan-ai.com/v1
|
||||
address: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
@@ -81,7 +81,7 @@ func convertInt(value interface{}) int {
|
||||
|
||||
func genAIKey(key string, provider string) string {
|
||||
keys := strings.Split(key, "@")
|
||||
return strings.TrimSuffix(keys[0], fmt.Sprintf("-%s", provider))
|
||||
return strings.TrimPrefix(keys[0], fmt.Sprintf("%s-", provider))
|
||||
}
|
||||
|
||||
// HandleMessage 处理从 NSQ 读取的消息
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ func (i *imlLocalModelController) initAILocalService(ctx context.Context, model
|
||||
})
|
||||
|
||||
return func() error {
|
||||
path := fmt.Sprintf("/%s/chat", strings.Trim(prefix, "/"))
|
||||
path := fmt.Sprintf("/%s/chat/completions", strings.Trim(prefix, "/"))
|
||||
timeout := 300000
|
||||
retry := 0
|
||||
aiPrompt := &ai_api_dto.AiPrompt{
|
||||
|
||||
@@ -3,6 +3,7 @@ package catalogue
|
||||
import (
|
||||
"github.com/APIParkLab/APIPark/module/catalogue"
|
||||
catalogue_dto "github.com/APIParkLab/APIPark/module/catalogue/dto"
|
||||
"github.com/APIParkLab/APIPark/module/service"
|
||||
"github.com/APIParkLab/APIPark/module/tag"
|
||||
tag_dto "github.com/APIParkLab/APIPark/module/tag/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -14,6 +15,7 @@ var (
|
||||
|
||||
type imlCatalogueController struct {
|
||||
catalogueModule catalogue.ICatalogueModule `autowired:""`
|
||||
appModule service.IAppModule `autowired:""`
|
||||
tagModule tag.ITagModule `autowired:""`
|
||||
}
|
||||
|
||||
@@ -26,7 +28,17 @@ func (i *imlCatalogueController) Subscribe(ctx *gin.Context, subscribeInfo *cata
|
||||
}
|
||||
|
||||
func (i *imlCatalogueController) ServiceDetail(ctx *gin.Context, sid string) (*catalogue_dto.ServiceDetail, error) {
|
||||
return i.catalogueModule.ServiceDetail(ctx, sid)
|
||||
detail, err := i.catalogueModule.ServiceDetail(ctx, sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, canSubscribe, err := i.appModule.SearchCanSubscribe(ctx, sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
detail.CanSubscribe = canSubscribe
|
||||
return detail, nil
|
||||
|
||||
}
|
||||
|
||||
func (i *imlCatalogueController) Search(ctx *gin.Context, keyword string) ([]*catalogue_dto.Item, []*tag_dto.Item, error) {
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
application_authorization "github.com/APIParkLab/APIPark/module/application-authorization"
|
||||
|
||||
mcp_server "github.com/APIParkLab/APIPark/mcp-server"
|
||||
"github.com/APIParkLab/APIPark/module/mcp"
|
||||
"github.com/APIParkLab/APIPark/module/system"
|
||||
"github.com/eolinker/go-common/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
mcp2 "github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
var _ IMcpController = (*imlMcpController)(nil)
|
||||
|
||||
type imlMcpController struct {
|
||||
settingModule system.ISettingModule `autowired:""`
|
||||
authorizationModule application_authorization.IAuthorizationModule `autowired:""`
|
||||
mcpModule mcp.IMcpModule `autowired:""`
|
||||
sessionKeys sync.Map
|
||||
server map[string]http.Handler
|
||||
openServer http.Handler
|
||||
}
|
||||
|
||||
var mcpDefaultConfig = `{
|
||||
"mcpServers": {
|
||||
"%s": {
|
||||
"url": "%s"
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func (i *imlMcpController) GlobalMCPConfig(ctx *gin.Context) (string, error) {
|
||||
cfg := i.settingModule.Get(ctx)
|
||||
if cfg.SitePrefix == "" {
|
||||
return "", fmt.Errorf("site prefix is empty")
|
||||
}
|
||||
return fmt.Sprintf(mcpDefaultConfig, "APIPark-MCP-Server", fmt.Sprintf("%s/openapi/v1/%s/sse?apikey={your_api_key}", strings.TrimSuffix(cfg.SitePrefix, "/"), mcp_server.GlobalBasePath)), nil
|
||||
}
|
||||
|
||||
func (i *imlMcpController) generateZhCNMCPServer() *server.MCPServer {
|
||||
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"service_list",
|
||||
mcp2.WithDescription("此工具用于获取 APIPark 中已注册服务的列表。每个服务包含其唯一标识(service ID)、名称、描述及包含的 API 列表等关键信息。支持通过关键词进行模糊搜索,以便快速缩小查找范围。在获得某个服务的 ID 后,可以调用 openapi_document 工具来获取该服务的 OpenAPI 文档,以便后续调用其提供的 API 接口。"),
|
||||
mcp2.WithString("keyword", mcp2.Description("关键词,用于模糊搜索服务")),
|
||||
),
|
||||
i.mcpModule.Services,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"openapi_document",
|
||||
mcp2.WithDescription("此工具用于获取指定服务的 OpenAPI 接口文档。返回内容支持 OpenAPI v3 与 v2 两种规范格式。通过传入服务 ID,可以查看该服务的所有 API 定义、参数结构、请求方式等详细信息,为后续构造请求做准备。"),
|
||||
mcp2.WithString("service", mcp2.Description("服务的唯一标识 ID")),
|
||||
),
|
||||
i.mcpModule.APIs,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"invoke_api",
|
||||
mcp2.WithDescription("此工具用于直接调用指定的 API 接口。调用前需根据该接口的 OpenAPI 文档构造必要的请求参数,如请求路径、方法、查询参数、请求头、请求体等。调用过程中无需传递认证信息,例如请求头中的 Authorization 字段不需要提供。"),
|
||||
mcp2.WithString("path", mcp2.Description("API 请求路径"), mcp2.Required()),
|
||||
mcp2.WithString("method", mcp2.Description("API 请求方法,例如 GET、POST、PUT"), mcp2.Required()),
|
||||
mcp2.WithString("content-type", mcp2.Description("请求的 Content-Type 类型。如果方法为 POST、PUT 或 PATCH,则必须指定该字段。")),
|
||||
mcp2.WithObject("query", mcp2.Description("请求的查询参数,类型为 map[string]string")),
|
||||
mcp2.WithObject("header", mcp2.Description("请求的头部参数,类型为 map[string]string")),
|
||||
mcp2.WithString("body", mcp2.Description("请求体内容,通常为 JSON 字符串")),
|
||||
),
|
||||
i.mcpModule.Invoke,
|
||||
)
|
||||
return s
|
||||
}
|
||||
|
||||
func (i *imlMcpController) generateZhTWMCPServer() *server.MCPServer {
|
||||
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"service_list",
|
||||
mcp2.WithDescription("此工具用於獲取 APIPark 中已註冊服務的清單。每個服務包含其唯一識別碼(service ID)、名稱、描述以及該服務所包含的 API 列表。支援關鍵字模糊搜尋,可快速縮小查詢範圍。獲取到服務 ID 後,可使用 openapi_document 工具來查詢該服務對應的 OpenAPI 文件,為後續 API 呼叫做準備。"),
|
||||
mcp2.WithString("keyword", mcp2.Description("關鍵字,用於模糊搜尋服務")),
|
||||
),
|
||||
i.mcpModule.Services,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"openapi_document",
|
||||
mcp2.WithDescription("此工具用於查詢指定服務的 OpenAPI 文件。返回的格式支援 OpenAPI v3 與 v2 標準。透過輸入服務 ID,可查閱該服務所有 API 的定義、參數結構、請求方式等細節,有助於後續構造 API 呼叫請求。"),
|
||||
mcp2.WithString("service", mcp2.Description("欲查詢的服務唯一識別碼")),
|
||||
),
|
||||
i.mcpModule.APIs,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"invoke_api",
|
||||
mcp2.WithDescription("此工具可直接發送 API 請求。在呼叫此工具之前,需根據該 API 的 OpenAPI 文件構造所需的請求參數,如請求路徑、方法、查詢參數、標頭、主體等。使用此工具時不需傳送任何認證資訊,例如 Authorization 標頭可省略。"),
|
||||
mcp2.WithString("path", mcp2.Description("API 的請求路徑"), mcp2.Required()),
|
||||
mcp2.WithString("method", mcp2.Description("API 的請求方法,例如 GET、POST、PUT"), mcp2.Required()),
|
||||
mcp2.WithString("content-type", mcp2.Description("請求的 Content-Type。若方法為 POST、PUT 或 PATCH,則必須指定")),
|
||||
mcp2.WithObject("query", mcp2.Description("請求的查詢參數,類型為 map[string]string")),
|
||||
mcp2.WithObject("header", mcp2.Description("請求的標頭,類型為 map[string]string")),
|
||||
mcp2.WithString("body", mcp2.Description("請求主體內容,通常為 JSON 字串")),
|
||||
),
|
||||
i.mcpModule.Invoke,
|
||||
)
|
||||
return s
|
||||
}
|
||||
|
||||
func (i *imlMcpController) generateEnMCPServer() *server.MCPServer {
|
||||
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"service_list",
|
||||
mcp2.WithDescription("This tool can retrieve a list of registered services on APIPark, including key information such as service ID, name, description, and API list within the service. Support keyword search to quickly narrow down the search scope. After obtaining the service ID, you can use this ID to call the tool openapi_document to obtain the openapi document of the service for the corresponding service, preparing for subsequent API calls."),
|
||||
mcp2.WithString("keyword", mcp2.Description("Keyword for fuzzy search")),
|
||||
),
|
||||
i.mcpModule.Services,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"openapi_document",
|
||||
mcp2.WithDescription("This tool returns the openAPI documentation for the corresponding service. The format supports the specifications of OpenAPI v3 and OpenAPI v2."),
|
||||
mcp2.WithString("service", mcp2.Description("Service ID")),
|
||||
),
|
||||
i.mcpModule.APIs,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"invoke_api",
|
||||
mcp2.WithDescription("This tool can directly make API calls. Before calling this tool, it is necessary to construct relevant parameters based on the corresponding API's openAPI documentation, including query, header, body, method, path, and other parameters. By using this tool, no authentication related information needs to be transmitted, that is, no request header Authorization needs to be transmitted."),
|
||||
mcp2.WithString("path", mcp2.Description("API path"), mcp2.Required()),
|
||||
mcp2.WithString("method", mcp2.Description("API method"), mcp2.Required()),
|
||||
mcp2.WithString("content-type", mcp2.Description("API Request Content-Type. If method is POST,PUT,PATCH, it must be set. If not set, it will be ignored.")),
|
||||
mcp2.WithObject("query", mcp2.Description("API Request query,param type is map[string]string")),
|
||||
mcp2.WithObject("header", mcp2.Description("API Request header,param type is map[string]string")),
|
||||
mcp2.WithString("body", mcp2.Description("API Request body")),
|
||||
),
|
||||
i.mcpModule.Invoke,
|
||||
)
|
||||
return s
|
||||
}
|
||||
|
||||
func (i *imlMcpController) generateJPMCPServer() *server.MCPServer {
|
||||
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"service_list",
|
||||
mcp2.WithDescription("このツールは、APIPark に登録されているサービスの一覧を取得するためのものです。各サービスには、サービスID、名称、説明、およびそのサービスに含まれるAPI一覧といった重要な情報が含まれます。キーワードによるあいまい検索が可能で、目的のサービスを素早く絞り込むことができます。取得したサービスIDを使用して openapi_document ツールを呼び出すことで、そのサービスの OpenAPI ドキュメントを取得でき、APIの利用準備が整います。"),
|
||||
mcp2.WithString("keyword", mcp2.Description("キーワード。サービスをあいまい検索するための文字列")),
|
||||
),
|
||||
i.mcpModule.Services,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"openapi_document",
|
||||
mcp2.WithDescription("指定されたサービスの OpenAPI ドキュメントを取得するためのツールです。OpenAPI v3 および v2 のフォーマットに対応しています。このドキュメントを使用することで、APIのエンドポイント、リクエスト方法、パラメータなどの詳細を確認でき、API呼び出しの準備に役立ちます。"),
|
||||
mcp2.WithString("service", mcp2.Description("対象のサービスID")),
|
||||
),
|
||||
i.mcpModule.APIs,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"invoke_api",
|
||||
mcp2.WithDescription("このツールは、指定された API を直接呼び出すためのものです。呼び出し前に、OpenAPI ドキュメントに基づいて必要なパラメータ(パス、メソッド、クエリ、ヘッダー、ボディなど)を構築する必要があります。呼び出し時に認証情報(例:Authorization ヘッダー)を送信する必要はありません。"),
|
||||
mcp2.WithString("path", mcp2.Description("API のリクエストパス"), mcp2.Required()),
|
||||
mcp2.WithString("method", mcp2.Description("HTTPメソッド(GET、POST、PUTなど)。"), mcp2.Required()),
|
||||
mcp2.WithString("content-type", mcp2.Description("リクエストの Content-Type。メソッドが POST、PUT、PATCH の場合に必須。")),
|
||||
mcp2.WithObject("query", mcp2.Description("リクエストのクエリパラメータ。型は map[string]string")),
|
||||
mcp2.WithObject("header", mcp2.Description("リクエストヘッダー。型は map[string]string")),
|
||||
mcp2.WithString("body", mcp2.Description("リクエストボディ。通常はJSON文字列")),
|
||||
),
|
||||
i.mcpModule.Invoke,
|
||||
)
|
||||
return s
|
||||
}
|
||||
|
||||
func (i *imlMcpController) OnComplete() {
|
||||
i.server = make(map[string]http.Handler)
|
||||
enSer := i.generateEnMCPServer()
|
||||
i.server["en-US"] = server.NewSSEServer(enSer, server.WithBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
||||
i.server["zh-CN"] = server.NewSSEServer(i.generateZhCNMCPServer(), server.WithBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
||||
i.server["zh-TW"] = server.NewSSEServer(i.generateZhTWMCPServer(), server.WithBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
||||
i.server["ja-JP"] = server.NewSSEServer(i.generateJPMCPServer(), server.WithBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
||||
|
||||
i.openServer = server.NewSSEServer(enSer, server.WithBasePath(fmt.Sprintf("/openapi/v1/%s", strings.Trim(mcp_server.GlobalBasePath, "/"))))
|
||||
}
|
||||
|
||||
func (i *imlMcpController) GlobalMCPHandle(ctx *gin.Context) {
|
||||
cfg := i.settingModule.Get(ctx)
|
||||
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
|
||||
locale := utils.I18n(ctx)
|
||||
if v, ok := i.server[locale]; ok {
|
||||
v.ServeHTTP(ctx.Writer, req)
|
||||
return
|
||||
}
|
||||
i.server["en-US"].ServeHTTP(ctx.Writer, req)
|
||||
}
|
||||
|
||||
func (i *imlMcpController) GlobalHandleSSE(ctx *gin.Context) {
|
||||
apikey := ctx.Request.URL.Query().Get("apikey")
|
||||
i.handleSSE(ctx, i.openServer, apikey)
|
||||
}
|
||||
|
||||
func (i *imlMcpController) handleSSE(ctx *gin.Context, server http.Handler, apikey string) {
|
||||
|
||||
writer := &ResponseWriter{
|
||||
Writer: ctx.Writer,
|
||||
sessionId: make(chan string),
|
||||
}
|
||||
defer close(writer.sessionId)
|
||||
sessionId := ""
|
||||
go func() {
|
||||
var ok bool
|
||||
sessionId, ok = <-writer.sessionId
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
i.sessionKeys.Store(sessionId, apikey)
|
||||
}()
|
||||
server.ServeHTTP(writer, ctx.Request)
|
||||
i.sessionKeys.Delete(sessionId)
|
||||
}
|
||||
|
||||
func (i *imlMcpController) GlobalHandleMessage(ctx *gin.Context) {
|
||||
i.handleMessage(ctx, i.openServer)
|
||||
}
|
||||
|
||||
func (i *imlMcpController) MCPHandle(ctx *gin.Context) {
|
||||
cfg := i.settingModule.Get(ctx)
|
||||
|
||||
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
|
||||
mcp_server.ServeHTTP(ctx.Writer, req)
|
||||
}
|
||||
|
||||
func (i *imlMcpController) ServiceHandleSSE(ctx *gin.Context) {
|
||||
apikey := ctx.Request.URL.Query().Get("apikey")
|
||||
serviceId := ctx.Param("serviceId")
|
||||
if serviceId == "" {
|
||||
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid service id", "success": "fail"})
|
||||
return
|
||||
}
|
||||
ok, err := i.authorizationModule.CheckAPIKeyAuthorization(ctx, serviceId, apikey)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": err.Error(), "success": "fail"})
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid apikey", "success": "fail"})
|
||||
return
|
||||
}
|
||||
|
||||
i.handleSSE(ctx, mcp_server.DefaultMCPServer(), apikey)
|
||||
}
|
||||
|
||||
func (i *imlMcpController) ServiceHandleMessage(ctx *gin.Context) {
|
||||
i.handleMessage(ctx, mcp_server.DefaultMCPServer())
|
||||
}
|
||||
|
||||
func (i *imlMcpController) handleMessage(ctx *gin.Context, server http.Handler) {
|
||||
sessionId := ctx.Request.URL.Query().Get("sessionId")
|
||||
apikey, ok := i.sessionKeys.Load(sessionId)
|
||||
if !ok {
|
||||
ctx.String(403, "sessionId not found")
|
||||
return
|
||||
}
|
||||
cfg := i.settingModule.Get(ctx)
|
||||
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
|
||||
req = req.WithContext(utils.SetLabel(req.Context(), "apikey", apikey.(string)))
|
||||
server.ServeHTTP(ctx.Writer, req)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/eolinker/go-common/autowire"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type IMcpController interface {
|
||||
MCPHandle(ctx *gin.Context)
|
||||
GlobalMCPHandle(ctx *gin.Context)
|
||||
GlobalHandleSSE(ctx *gin.Context)
|
||||
GlobalHandleMessage(ctx *gin.Context)
|
||||
ServiceHandleSSE(ctx *gin.Context)
|
||||
ServiceHandleMessage(ctx *gin.Context)
|
||||
GlobalMCPConfig(ctx *gin.Context) (string, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
autowire.Auto[IMcpController](func() reflect.Value {
|
||||
return reflect.ValueOf(new(imlMcpController))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type ResponseWriter struct {
|
||||
Writer http.ResponseWriter
|
||||
sessionId chan string
|
||||
}
|
||||
|
||||
func (r *ResponseWriter) Flush() {
|
||||
fluster, ok := r.Writer.(http.Flusher)
|
||||
if ok {
|
||||
fluster.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ResponseWriter) Header() http.Header {
|
||||
return r.Writer.Header()
|
||||
}
|
||||
|
||||
func (r *ResponseWriter) Write(bytes []byte) (int, error) {
|
||||
re := regexp.MustCompile(`sessionId=([^&?\s]+)`)
|
||||
match := re.FindStringSubmatch(string(bytes))
|
||||
if len(match) > 1 {
|
||||
r.sessionId <- match[1]
|
||||
}
|
||||
return r.Writer.Write(bytes)
|
||||
}
|
||||
|
||||
func (r *ResponseWriter) WriteHeader(statusCode int) {
|
||||
r.Writer.WriteHeader(statusCode)
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
@@ -354,7 +544,8 @@ func (i *imlServiceController) createAIService(ctx *gin.Context, teamID string,
|
||||
if !has {
|
||||
return nil, fmt.Errorf("model %s not found", pv.DefaultLLM)
|
||||
}
|
||||
modelId = m.ID()
|
||||
//modelId = m.ID()
|
||||
modelId = m.Name()
|
||||
modelCfg = m.DefaultConfig()
|
||||
|
||||
}
|
||||
@@ -541,8 +732,9 @@ type imlAppController struct {
|
||||
authModule application_authorization.IAuthorizationModule `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlAppController) SearchCanSubscribe(ctx *gin.Context, serviceId string) ([]*service_dto.SimpleAppItem, error) {
|
||||
return i.module.SearchCanSubscribe(ctx, serviceId)
|
||||
func (i *imlAppController) SearchCanSubscribe(ctx *gin.Context, serviceId string) ([]*service_dto.SubscribeAppItem, error) {
|
||||
items, _, err := i.module.SearchCanSubscribe(ctx, serviceId)
|
||||
return items, err
|
||||
}
|
||||
|
||||
func (i *imlAppController) Search(ctx *gin.Context, teamId string, keyword string) ([]*service_dto.AppItem, error) {
|
||||
|
||||
@@ -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 {
|
||||
@@ -44,7 +59,7 @@ type IAppController interface {
|
||||
// SimpleApps 获取简易项目列表
|
||||
SimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error)
|
||||
MySimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error)
|
||||
SearchCanSubscribe(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error)
|
||||
SearchCanSubscribe(ctx *gin.Context, serviceId string) ([]*service_dto.SubscribeAppItem, error)
|
||||
GetApp(ctx *gin.Context, appId string) (*service_dto.App, error)
|
||||
DeleteApp(ctx *gin.Context, appId string) error
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package system_apikey
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
system_apikey_dto "github.com/APIParkLab/APIPark/module/system-apikey/dto"
|
||||
"github.com/eolinker/go-common/autowire"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type IAPIKeyController interface {
|
||||
Create(ctx *gin.Context, input *system_apikey_dto.Create) error
|
||||
Update(ctx *gin.Context, id string, input *system_apikey_dto.Update) error
|
||||
Delete(ctx *gin.Context, id string) error
|
||||
Get(ctx *gin.Context, id string) (*system_apikey_dto.APIKey, error)
|
||||
Search(ctx *gin.Context, keyword string) ([]*system_apikey_dto.Item, error)
|
||||
SimpleList(ctx *gin.Context) ([]*system_apikey_dto.SimpleItem, error)
|
||||
MyAPIKeys(ctx *gin.Context) ([]*system_apikey_dto.SimpleItem, error)
|
||||
MyAPIKeysByService(ctx *gin.Context, serviceId string) ([]*system_apikey_dto.AuthorizationItem, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
autowire.Auto[IAPIKeyController](func() reflect.Value {
|
||||
return reflect.ValueOf(new(imlAPIKeyController))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package system_apikey
|
||||
|
||||
import (
|
||||
system_apikey "github.com/APIParkLab/APIPark/module/system-apikey"
|
||||
system_apikey_dto "github.com/APIParkLab/APIPark/module/system-apikey/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var _ IAPIKeyController = new(imlAPIKeyController)
|
||||
|
||||
type imlAPIKeyController struct {
|
||||
apikeyModule system_apikey.IAPIKeyModule `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlAPIKeyController) MyAPIKeysByService(ctx *gin.Context, serviceId string) ([]*system_apikey_dto.AuthorizationItem, error) {
|
||||
return i.apikeyModule.MyAPIKeysByService(ctx, serviceId)
|
||||
}
|
||||
|
||||
func (i *imlAPIKeyController) MyAPIKeys(ctx *gin.Context) ([]*system_apikey_dto.SimpleItem, error) {
|
||||
return i.apikeyModule.MyAPIKeys(ctx)
|
||||
}
|
||||
|
||||
func (i *imlAPIKeyController) Create(ctx *gin.Context, input *system_apikey_dto.Create) error {
|
||||
return i.apikeyModule.Create(ctx, input)
|
||||
}
|
||||
|
||||
func (i *imlAPIKeyController) Update(ctx *gin.Context, id string, input *system_apikey_dto.Update) error {
|
||||
return i.apikeyModule.Update(ctx, id, input)
|
||||
}
|
||||
|
||||
func (i *imlAPIKeyController) Delete(ctx *gin.Context, id string) error {
|
||||
return i.apikeyModule.Delete(ctx, id)
|
||||
}
|
||||
|
||||
func (i *imlAPIKeyController) Get(ctx *gin.Context, id string) (*system_apikey_dto.APIKey, error) {
|
||||
return i.apikeyModule.Get(ctx, id)
|
||||
}
|
||||
|
||||
func (i *imlAPIKeyController) Search(ctx *gin.Context, keyword string) ([]*system_apikey_dto.Item, error) {
|
||||
return i.apikeyModule.Search(ctx, keyword)
|
||||
}
|
||||
|
||||
func (i *imlAPIKeyController) SimpleList(ctx *gin.Context) ([]*system_apikey_dto.SimpleItem, error) {
|
||||
return i.apikeyModule.SimpleList(ctx)
|
||||
}
|
||||
@@ -395,36 +395,36 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := fmt.Sprintf("/%s/demo_translation_api", strings.Trim(input.Prefix, "/"))
|
||||
path := fmt.Sprintf("/%s/chat/completions", strings.Trim(input.Prefix, "/"))
|
||||
timeout := 300000
|
||||
retry := 0
|
||||
aiPrompt := &ai_api_dto.AiPrompt{
|
||||
Variables: []*ai_api_dto.AiPromptVariable{
|
||||
{
|
||||
Key: "source_lang",
|
||||
Description: "",
|
||||
Require: true,
|
||||
},
|
||||
{
|
||||
Key: "target_lang",
|
||||
Description: "",
|
||||
Require: true,
|
||||
},
|
||||
{
|
||||
Key: "text",
|
||||
Description: "",
|
||||
Require: true,
|
||||
},
|
||||
},
|
||||
Prompt: "You need to translate {{source_lang}} into {{target_lang}}, and the following is the content that needs to be translated.\n---\n{{text}}",
|
||||
//Variables: []*ai_api_dto.AiPromptVariable{
|
||||
// {
|
||||
// Key: "source_lang",
|
||||
// Description: "",
|
||||
// Require: true,
|
||||
// },
|
||||
// {
|
||||
// Key: "target_lang",
|
||||
// Description: "",
|
||||
// Require: true,
|
||||
// },
|
||||
// {
|
||||
// Key: "text",
|
||||
// Description: "",
|
||||
// Require: true,
|
||||
// },
|
||||
//},
|
||||
//Prompt: "You need to translate {{source_lang}} into {{target_lang}}, and the following is the content that needs to be translated.\n---\n{{text}}",
|
||||
}
|
||||
aiModel := &ai_api_dto.AiModel{
|
||||
Id: m.ID(),
|
||||
Config: m.DefaultConfig(),
|
||||
Provider: providerId,
|
||||
}
|
||||
name := "Demo Translation API"
|
||||
description := "A demo that shows you how to use a prompt to create a Translation API."
|
||||
name := "Demo Chat API"
|
||||
description := "A demo that shows you how to use a prompt to create a Chat API."
|
||||
apiId := uuid.New().String()
|
||||
err = i.aiAPIModule.Create(
|
||||
ctx,
|
||||
|
||||
@@ -47,7 +47,11 @@
|
||||
"swagger-ui-react": "^5.17.14",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"uuid": "^9.0.1",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"react-json-view": "^1.21.3",
|
||||
"zod": "^3.23.8",
|
||||
"@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()
|
||||
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
import { Breadcrumb } from 'antd'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { FC, useEffect } from 'react'
|
||||
import { LeftOutlined } from '@ant-design/icons'
|
||||
|
||||
const TopBreadcrumb: FC = () => {
|
||||
const TopBreadcrumb: FC<{ handleBackCallback?: () => void }> = ({ handleBackCallback }) => {
|
||||
const { breadcrumb } = useBreadcrumb()
|
||||
useEffect(() => {}, [breadcrumb])
|
||||
return <Breadcrumb items={breadcrumb} />
|
||||
const handleBack = () => {
|
||||
handleBackCallback?.()
|
||||
}
|
||||
return (
|
||||
<div className="flex text-[18px] leading-[25px] pb-[12px]">
|
||||
<div
|
||||
onClick={handleBack}
|
||||
className="hover:bg-gray-100 items-center mt-[1px] mr-[12px] flex justify-center rounded-lg border cursor-pointer border-gray-300 w-[30px] h-[30px] border border-solid "
|
||||
>
|
||||
<LeftOutlined className="text-xs" />
|
||||
</div>
|
||||
<Breadcrumb items={breadcrumb} className="flex-1" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TopBreadcrumb
|
||||
|
||||
@@ -4,11 +4,12 @@ import { $t } from '@common/locales'
|
||||
import { Button, Tag } from 'antd'
|
||||
import { FC, ReactNode } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import TopBreadcrumb from './Breadcrumb'
|
||||
|
||||
class InsidePageProps {
|
||||
showBanner?: boolean = true
|
||||
pageTitle: string | React.ReactNode = ''
|
||||
tagList?: Array<{ label: string | ReactNode }> = []
|
||||
tagList?: Array<{ label: string | ReactNode; className?: string; color?: string }> = []
|
||||
children: React.ReactNode
|
||||
showBtn?: boolean = false
|
||||
btnTitle?: string = ''
|
||||
@@ -25,6 +26,7 @@ class InsidePageProps {
|
||||
scrollInsidePage?: boolean = false
|
||||
customPadding?: boolean
|
||||
customBtn?: ReactNode
|
||||
customBanner?: ReactNode
|
||||
}
|
||||
|
||||
const InsidePage: FC<InsidePageProps> = ({
|
||||
@@ -45,7 +47,8 @@ const InsidePage: FC<InsidePageProps> = ({
|
||||
scrollPage = true,
|
||||
scrollInsidePage = false,
|
||||
customPadding = false,
|
||||
customBtn
|
||||
customBtn,
|
||||
customBanner
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -60,18 +63,16 @@ 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 && (
|
||||
<div className="text-[18px] leading-[25px] mb-[12px]">
|
||||
<Button type="text" onClick={goBack}>
|
||||
<ArrowLeftOutlined className="max-h-[14px]" />
|
||||
{$t('返回')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{backUrl && <TopBreadcrumb handleBackCallback={() => goBack()} />}
|
||||
<div className="flex justify-between mb-[20px] items-center ">
|
||||
<div className="flex items-center gap-TAG_LEFT">
|
||||
<div className="text-theme text-[26px] ">{pageTitle}</div>
|
||||
@@ -79,7 +80,7 @@ const InsidePage: FC<InsidePageProps> = ({
|
||||
tagList?.length > 0 &&
|
||||
tagList?.map((tag) => {
|
||||
return (
|
||||
<Tag key={tag.label as string} bordered={false}>
|
||||
<Tag key={tag.label as string} bordered={false} color={tag.color} className={tag.className}>
|
||||
{tag.label}
|
||||
</Tag>
|
||||
)
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { App, Col, Form, Input, Row, Table, Tooltip } from 'antd'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react'
|
||||
import {
|
||||
PublishApprovalInfoType,
|
||||
PublishApprovalModalHandle,
|
||||
@@ -36,6 +36,7 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
const { state } = useGlobalContext()
|
||||
const versionInputRef = useRef<Input>(null)
|
||||
|
||||
const save: (operate: 'pass' | 'refuse') => Promise<boolean | string> = (operate) => {
|
||||
if (type === 'view') {
|
||||
@@ -140,6 +141,12 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ opinion: '', ...data })
|
||||
// 如果是添加模式且insidePage为true,自动聚焦版本号输入框
|
||||
if (type === 'add' && insidePage && versionInputRef.current) {
|
||||
setTimeout(() => {
|
||||
versionInputRef.current?.focus()
|
||||
}, 100)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const translatedUpstreamColumns = useMemo(
|
||||
@@ -335,7 +342,12 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
|
||||
{insidePage && (
|
||||
<>
|
||||
<Form.Item label={$t('版本号')} name="version" rules={[{ required: true, whitespace: true }]}>
|
||||
<Input className="w-INPUT_NORMAL" disabled={type !== 'add'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
<Input
|
||||
className="w-INPUT_NORMAL"
|
||||
disabled={type !== 'add'}
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
ref={versionInputRef}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={$t('版本说明')} name="versionRemark">
|
||||
|
||||
@@ -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 : (
|
||||
|
||||
@@ -55,6 +55,8 @@ export const TranslateWord = () => {
|
||||
{$t('打开 OpenAPI YAML 编辑器')}
|
||||
{$t('无需审核:允许任何消费者调用该服务')}
|
||||
{$t('人工审核:仅允许通过人工审核的消费者调用该服务')}
|
||||
{$t('开启:AI Agent 等产品能够通过 MCP 方式调用服务')}
|
||||
{$t('总览')}
|
||||
{$t('永久')}
|
||||
{$t('否')}
|
||||
{$t('是')}
|
||||
@@ -185,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
|
||||
@@ -234,12 +234,16 @@ export default function ApiEdit({
|
||||
<>
|
||||
<Space.Compact className="w-full mb-btnybase">
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-[15%] min-w-[100px]"
|
||||
value={apiInfo?.protocol || 'HTTP'}
|
||||
disabled={true}
|
||||
options={protocolOptionList}
|
||||
/>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-[15%] min-w-[100px]"
|
||||
value={apiInfo?.method}
|
||||
disabled={true}
|
||||
|
||||
@@ -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]
|
||||
@@ -254,6 +254,21 @@ export const PERMISSION_DEFINITION = [
|
||||
anyOf: [{ backend: ['system.settings.ai_balance.manager'] }]
|
||||
}
|
||||
},
|
||||
'system.settings.mcp.manager': {
|
||||
granted: {
|
||||
anyOf: [{ backend: ['system.settings.mcp.manager'] }]
|
||||
}
|
||||
},
|
||||
'system.settings.mcp.view': {
|
||||
granted: {
|
||||
anyOf: [{ backend: ['system.settings.mcp.view'] }]
|
||||
}
|
||||
},
|
||||
'system.settings.apikey.view': {
|
||||
granted: {
|
||||
anyOf: [{ backend: ['system.settings.apikey.view'] }]
|
||||
}
|
||||
},
|
||||
'system.devops.policy.view': {
|
||||
granted: {
|
||||
anyOf: [{ backend: ['system.settings.strategy.view'] }]
|
||||
|
||||
@@ -81,6 +81,7 @@ export type MatchItem = {
|
||||
export type EntityItem = {
|
||||
id: string
|
||||
name: string
|
||||
isSubscribed: boolean
|
||||
}
|
||||
|
||||
export type DynamicMenuItem = {
|
||||
|
||||
@@ -18,5 +18,21 @@ export const useBreadcrumb = () => {
|
||||
export const BreadcrumbProvider = ({ children }: unknown) => {
|
||||
const [breadcrumb, setBreadcrumb] = useState<BreadcrumbItemType[]>([])
|
||||
|
||||
return <BreadcrumbContext.Provider value={{ setBreadcrumb, breadcrumb }}>{children}</BreadcrumbContext.Provider>
|
||||
return (
|
||||
<BreadcrumbContext.Provider
|
||||
value={{
|
||||
setBreadcrumb: (newItems) => {
|
||||
newItems.forEach((item) => {
|
||||
item.title = (
|
||||
<span className={`${item.onClick ? 'cursor-pointer hover:text-theme' : ''}`}>{item.title}</span>
|
||||
)
|
||||
})
|
||||
setBreadcrumb(newItems)
|
||||
},
|
||||
breadcrumb
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</BreadcrumbContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
},
|
||||
@@ -99,10 +99,31 @@ const mockData = [
|
||||
icon: 'ic:baseline-bar-chart',
|
||||
children: [
|
||||
{
|
||||
name: '运行视图',
|
||||
key: 'analytics',
|
||||
path: '/analytics',
|
||||
icon: 'ic:baseline-bar-chart',
|
||||
name: '总览',
|
||||
key: 'analyticsTotal',
|
||||
path: '/analytics/total',
|
||||
icon: 'material-symbols:bar-chart',
|
||||
access: 'system.analysis.run_view.view'
|
||||
},
|
||||
{
|
||||
name: '服务',
|
||||
key: 'analyticsService',
|
||||
path: '/analytics/service/list',
|
||||
icon: 'ic:baseline-blinds-closed',
|
||||
access: 'system.analysis.run_view.view'
|
||||
},
|
||||
{
|
||||
name: '消费者',
|
||||
key: 'analyticsConsumer',
|
||||
path: '/analytics/consumer/list',
|
||||
icon: 'ic:baseline-apps',
|
||||
access: 'system.analysis.run_view.view'
|
||||
},
|
||||
{
|
||||
name: 'API',
|
||||
key: 'analyticsApi',
|
||||
path: '/analytics/api/list',
|
||||
icon: 'gravity-ui:plug-connection',
|
||||
access: 'system.analysis.run_view.view'
|
||||
}
|
||||
],
|
||||
@@ -196,6 +217,20 @@ const mockData = [
|
||||
key: 'maintenanceCenter',
|
||||
path: '/datasourcing',
|
||||
children: [
|
||||
{
|
||||
name: 'MCP 服务',
|
||||
key: 'mcpService',
|
||||
path: '/mcpService',
|
||||
icon: 'ph:network-x',
|
||||
access: 'system.settings.mcp.view'
|
||||
},
|
||||
{
|
||||
name: 'API Key',
|
||||
key: 'mcpKey',
|
||||
path: '/mcpKey',
|
||||
icon: 'material-symbols:key',
|
||||
access: 'system.settings.apikey.view'
|
||||
},
|
||||
{
|
||||
name: '数据源',
|
||||
key: 'datasourcing',
|
||||
@@ -218,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'
|
||||
}
|
||||
]
|
||||
@@ -220,6 +220,26 @@ const mockData = {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
driver: 'apipark.builtIn.component',
|
||||
name: 'mcpService',
|
||||
router: [
|
||||
{
|
||||
path: 'mcpService',
|
||||
type: 'normal'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
driver: 'apipark.builtIn.component',
|
||||
name: 'mcpKey',
|
||||
router: [
|
||||
{
|
||||
path: 'mcpKey',
|
||||
type: 'normal'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
driver: 'apipark.builtIn.component',
|
||||
name: 'loadBalancing',
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"添加(0)": "Ka7aaaeb",
|
||||
"请输入Key": "Kaff78ecf",
|
||||
"请输入Value": "K65d46535",
|
||||
"返回": "Kc14b2ea3",
|
||||
"ID": "K11d3633a",
|
||||
"名称": "Kbff43de3",
|
||||
"Driver": "K16ca79ef",
|
||||
@@ -55,6 +54,10 @@
|
||||
"上游列表": "K54e44357",
|
||||
"备注": "Kb8e8e6f5",
|
||||
"上线情况": "K7e52ffa3",
|
||||
"服务 ID": "K1e84ad04",
|
||||
"服务尚未发布": "Ke1e649cb",
|
||||
"跳转至详情页": "K2e683a7d",
|
||||
"暂无服务描述": "Ka4b45550",
|
||||
"申请原因": "K1ab0ae5b",
|
||||
"审核意见": "K53c00c3c",
|
||||
"暂无(0)权限,请联系管理员分配。": "Kfd50704d",
|
||||
@@ -114,6 +117,8 @@
|
||||
"打开 OpenAPI YAML 编辑器": "Kdac8ce7e",
|
||||
"无需审核:允许任何消费者调用该服务": "K1fc2cc28",
|
||||
"人工审核:仅允许通过人工审核的消费者调用该服务": "K8dabb98e",
|
||||
"开启:AI Agent 等产品能够通过 MCP 方式调用服务": "Ke959f135",
|
||||
"总览": "Kaf9e8011",
|
||||
"永久": "Kbfe02d7f",
|
||||
"否": "K1e9c479e",
|
||||
"是": "Kaddfcb6b",
|
||||
@@ -241,6 +246,9 @@
|
||||
"调用地址": "K2f5fdf5e",
|
||||
"消费者 IP": "K1bc5e0a3",
|
||||
"鉴权名称": "K6f39ea21",
|
||||
"日志输出": "K3c722abd",
|
||||
"响应时间": "K1be06929",
|
||||
"时间戳": "K5e51f5d",
|
||||
"暂无操作权限,请联系管理员分配。": "K23fda291",
|
||||
"微信小程序": "K4618cb0a",
|
||||
"获取文件,需填路径": "Ka854f511",
|
||||
@@ -360,7 +368,6 @@
|
||||
"管理": "K5974bf24",
|
||||
"调用拓扑图": "K3fa5c4c3",
|
||||
"设置": "Kb5c7b82d",
|
||||
"服务 ID": "K1e84ad04",
|
||||
"新增订阅方": "K39ab0358",
|
||||
"手动添加": "K18307d56",
|
||||
"订阅申请": "K705fe9f5",
|
||||
@@ -375,7 +382,6 @@
|
||||
"拦截接口": "Kee4139c2",
|
||||
"开启拦截后,网关会拦截所有该路径的请求。": "K3e38ea",
|
||||
"模型配置": "K8a35059b",
|
||||
"路由": "Kf9dcef3a",
|
||||
"添加路由": "K6134bbe8",
|
||||
"输入 URL 查找路由": "Kf85b83a0",
|
||||
"线上模型": "K84b2cf2d",
|
||||
@@ -414,17 +420,16 @@
|
||||
"启用": "K52c8a730",
|
||||
"Ollama 地址": "K6b99dce8",
|
||||
"输入例如:https://www.apipark.com": "K8d4f5b44",
|
||||
"自定义": "K8929cbb1",
|
||||
"自定义(空模板)": "K24f6a5b4",
|
||||
"模型名称": "K1fd51aaa",
|
||||
"访问配置": "Kd6285399",
|
||||
"模型参数模板": "K7eb03edd",
|
||||
"模型参数": "K49b434e9",
|
||||
"载入预置模板": "Kea608112",
|
||||
"供应商名称": "K16ef56b1",
|
||||
"注意:": "K484de451",
|
||||
"仅支持使用 OpenAI 输入输出格式和认证方法(APIKey)的供应商。如果不满足此条件,创建后自定义供应商将不可用。": "Kbd80dde0",
|
||||
"从 (0) 获取 API KEY": "Kb3e34847",
|
||||
"该模型为官方模型,不可编辑": "Kcb6a1c57",
|
||||
"存在使用当前模型的接口,需要先解绑后才能编辑": "Kf9300eb4",
|
||||
"该模型为官方模型,不可删除": "K8af71816",
|
||||
"存在使用当前模型的接口,需要先解绑后才能删除": "Kb8ad0af5",
|
||||
"模型值": "K73cb9ff1",
|
||||
@@ -450,6 +455,7 @@
|
||||
"Models": "Ke37a353f",
|
||||
"Keys": "K14bcebd2",
|
||||
"添加供应商": "Kd87397b0",
|
||||
"编辑供应商( (0) )": "Kee7de862",
|
||||
"编辑供应商": "K5bcf8c48",
|
||||
"(0) 模型": "Kf7a916be",
|
||||
"待审核": "K35612f29",
|
||||
@@ -457,7 +463,7 @@
|
||||
"发布申请": "K56b4254f",
|
||||
"API 调用地址": "Kea2f9279",
|
||||
"API base URL 一般设置为API 网关的外部网络访问地址,或者是API网关绑定的域名。": "K7fc496a1",
|
||||
"集成地址": "K508d8bf4",
|
||||
"OpenAPI & MCP 调用地址": "Ka7ca8fde",
|
||||
"与外部平台集成时,获取 API 市场中文档信息的域名": "K67f4e9bb",
|
||||
"常规设置": "K8ab0fc95",
|
||||
"API 请求设置": "Kb66fec9d",
|
||||
@@ -469,6 +475,7 @@
|
||||
"分类名称": "Ke595a20a",
|
||||
"父分类 ID": "K9679728f",
|
||||
"子分类名称": "K9b2d08fd",
|
||||
"暂无权限": "Kce2fcdbf",
|
||||
"添加 Rest 服务": "K2c93168c",
|
||||
"导入OpenAPI文档,将现有系统的API发布到APIPark。": "K39a8d392",
|
||||
"添加在线 AI API": "K68932d54",
|
||||
@@ -549,8 +556,19 @@
|
||||
"访客模式": "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",
|
||||
"MCP Service 充当 AI 模型与 API 之间的桥梁,允许智能助手(如 Claude)动态发现和调用 Gateway 上的 API,无需繁琐的手动配置或自定义集成。": "K7c2bfeff",
|
||||
"部门名称": "K33c76dbc",
|
||||
"父部门 ID": "K84829ca9",
|
||||
"子部门名称": "K4d7fc74b",
|
||||
@@ -607,7 +625,7 @@
|
||||
"数据源": "K8fa58214",
|
||||
"设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。": "Kdbafd6f9",
|
||||
"统计图表": "K1358acf",
|
||||
"数据日志": "K17dc3a62",
|
||||
"请求日志": "Kc8bf447",
|
||||
"地址(IP:端口)": "K62dabdf6",
|
||||
"组织(Organization)": "K2db12335",
|
||||
"添加策略": "K34d0d409",
|
||||
@@ -641,10 +659,33 @@
|
||||
"支持对系统全局进行统一的策略配置,从而简化管理并确保一致性。全局策略的优先级比服务策略略低。": "Kc975cd5a",
|
||||
"资源配置": "K8e7a0f80",
|
||||
"角色": "Kf644225f",
|
||||
"角色配置": "Kc9f2249c",
|
||||
"设置角色的权限范围。": "K95c3fd8b",
|
||||
"系统级别角色": "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",
|
||||
@@ -662,9 +703,13 @@
|
||||
"停止": "K24540de",
|
||||
"继续等待": "Kd85b3f64",
|
||||
"只允许上传PNG、JPG或SVG格式的图片": "Ka9c08390",
|
||||
"关闭 MCP": "K30595880",
|
||||
"关闭后将无法通过MCP方式调用服务": "Kc081047c",
|
||||
"了解": "Ka73a5801",
|
||||
"服务名称": "K413b9869",
|
||||
"服务类型": "K9919285b",
|
||||
"REST 服务": "K62840d62",
|
||||
"MCP": "K373c8ab3",
|
||||
"默认 AI 供应商": "Kcef64f4d",
|
||||
"创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API": "K300c89d4",
|
||||
"未配置任何 AI 模型供应商,": "Kcab588a9",
|
||||
@@ -723,7 +768,6 @@
|
||||
"退出全屏": "Kaf70c3b",
|
||||
"(0)调用详情": "Kd22841a4",
|
||||
"消费者调用统计": "K61cca533",
|
||||
"消费者": "K7acfcfad",
|
||||
"请选择消费者": "Kdfff59d4",
|
||||
"调用趋势": "K8c7f2d2e",
|
||||
"(0)-(1)调用趋势": "K657c3452",
|
||||
@@ -764,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",
|
||||
@@ -811,6 +854,7 @@
|
||||
"添加授权": "Kd23d1716",
|
||||
"到期时间": "Kfa920c0",
|
||||
"订阅的服务": "Kcce1af60",
|
||||
"返回": "Kc14b2ea3",
|
||||
"审核详情": "Kfefa9b58",
|
||||
"取消订阅": "K3118fdb0",
|
||||
"请确认是否取消订阅?": "Ked811bb1",
|
||||
@@ -823,22 +867,20 @@
|
||||
"创建并管理自己的消费者实体,每个消费者可以订阅多个API服务,确保在调用之前已获得相应权限。你可以为消费者生成 API 密钥等鉴权方式,用于安全地调用 API 服务": "K5c4e2865",
|
||||
"订阅的服务数量:已通过 (0) 个,申请中 (1) 个": "K3c7b175f",
|
||||
"输入名称、ID 查找消费者": "K3a6f905d",
|
||||
"服务市场": "K370a3eb2",
|
||||
"API 门户": "Kc84dbd1a",
|
||||
"服务详情": "Kf7ec36d",
|
||||
"申请服务": "K58ca9485",
|
||||
"介绍": "K59cdbec3",
|
||||
"Base URL": "Kc29dabf2",
|
||||
"申请": "K4aa9ed2c",
|
||||
"服务信息": "K6c060779",
|
||||
"接入消费者": "Kba74f26d",
|
||||
"供应方": "Kb97544cb",
|
||||
"分类": "Kb32f0afe",
|
||||
"版本": "K81634069",
|
||||
"更新时间": "Keefda53d",
|
||||
"介绍": "K59cdbec3",
|
||||
"API": "K3ba29a85",
|
||||
"无标签": "K96a2f1c8",
|
||||
"暂无服务描述": "Ka4b45550",
|
||||
"分类": "Kb32f0afe",
|
||||
"服务市场": "K370a3eb2",
|
||||
"API 数量": "K72b0c0b3",
|
||||
"接入消费者数量": "K70b79760",
|
||||
"30天内调用次数": "K3d52b756",
|
||||
"关联标签": "K96059c69",
|
||||
"更新者": "K8b7c2592",
|
||||
"添加 Open Api": "K32263abd",
|
||||
|
||||
@@ -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",
|
||||
@@ -498,7 +498,7 @@
|
||||
"K370a3eb2": "Service Marketplace",
|
||||
"Kf7ec36d": "Service Details",
|
||||
"K58ca9485": "Subscribe",
|
||||
"K59cdbec3": "Intro",
|
||||
"K59cdbec3": "Readme",
|
||||
"K4aa9ed2c": "Subscribe",
|
||||
"K6c060779": "Service Information",
|
||||
"Kb97544cb": "Provider",
|
||||
@@ -898,5 +898,79 @@
|
||||
"Kce2fcdbf": "No Permission",
|
||||
"K24f6a5b4": "Custom (Empty Template)",
|
||||
"Kea608112": "Load Preset Template",
|
||||
"Kee7de862": "Edit Provider( (0) )"
|
||||
"Kee7de862": "Edit Provider( (0) )",
|
||||
"Kb0e0aeda": "New API Key",
|
||||
"K9d81999c": "The API Key can be used to call system-level Open API and MCP.",
|
||||
"Ka7ca8fde": "OpenAPI & MCP Invocation Address",
|
||||
"K6e9c928f": "MCP Config",
|
||||
"Kb6d0eb39": "Open API Docs",
|
||||
"Ke6908f16": "Integration to AI Agent",
|
||||
"Kf106bc62": "MCP Service",
|
||||
"K7c2bfeff": "MCP Service acts as a bridge between AI models and APIs, enabling intelligent assistants (such as Claude) to dynamically discover and invoke APIs on the Gateway without the need for tedious manual configuration or custom integration.",
|
||||
"K30595880": "Disable MCP",
|
||||
"Kc081047c": "After turning it off, you won’t be able to use the service through the MCP.",
|
||||
"K373c8ab3": "MCP",
|
||||
"K3ba29a85": "API",
|
||||
"K3d52b756": "Number of invocations within 30 days",
|
||||
"Ke959f135": "Enable: AI Agent and other products can invoke services through MCP.",
|
||||
"Ka73a5801": "I Understand",
|
||||
"K6cd677b": "Documentation",
|
||||
"Kec68fe24": "Edit API Settings",
|
||||
"Kc9aeee51": "Add API Settings",
|
||||
"Kf06f6737": "Edit Service Policy",
|
||||
"K205971e1": "Add Service Policy",
|
||||
"Kc9f2249c": "Role Configuration",
|
||||
"Kba68dfc1": "Add API Settings",
|
||||
"Kc84dbd1a": "API Portal",
|
||||
"Ke93388fd": "Edit API",
|
||||
"K84aabfd4": "Add API",
|
||||
"K71ed51fa": "Please subscribe to the service first",
|
||||
"K1bec8cbe": "Select API Key",
|
||||
"K5611e01e": "This consumer is already subscribed",
|
||||
"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": "モデル",
|
||||
@@ -920,5 +920,79 @@
|
||||
"Kce2fcdbf": "権限がありません",
|
||||
"K24f6a5b4": "カスタム(空のテンプレート)",
|
||||
"Kea608112": "プリセットテンプレートを読み込む",
|
||||
"Kee7de862": "サプライヤーを編集( (0) )"
|
||||
"Kee7de862": "サプライヤーを編集( (0) )",
|
||||
"Kb0e0aeda": "APIキーを新規作成",
|
||||
"K9d81999c": "APIキーは、システムレベルのOpen APIおよびMCPの呼び出しに使用できます。",
|
||||
"Ka7ca8fde": "OpenAPI & MCP 呼び出しアドレス",
|
||||
"K6e9c928f": "MCP 設定",
|
||||
"Kb6d0eb39": "Open API ドキュメント",
|
||||
"Ke6908f16": "AI プロキシ統合",
|
||||
"Kf106bc62": "MCP サービス",
|
||||
"K7c2bfeff": "MCP サービスは、AI モデルと API の間の橋渡し役を果たし、スマートアシスタント(Claude など)が、面倒な手動設定やカスタム統合なしで、ゲートウェイ上の API を動的に発見し、呼び出すことを可能にします。",
|
||||
"K30595880": "MCP 無効化",
|
||||
"Kc081047c": "無効化すると、MCP を通じてサービスを呼び出すことができなくなります。",
|
||||
"K373c8ab3": "MCP",
|
||||
"K3ba29a85": "API",
|
||||
"K3d52b756": "30日以内の呼び出し回数",
|
||||
"Ke959f135": "有効化:AI Agent などの製品が MCP 経由でサービスを呼び出すことができる",
|
||||
"Ka73a5801": "理解する",
|
||||
"K6cd677b": "説明書",
|
||||
"Kec68fe24": "API設定の編集",
|
||||
"Kc9aeee51": "API設定の追加",
|
||||
"Kf06f6737": "サービスポリシーの編集",
|
||||
"K205971e1": "サービスポリシーの追加",
|
||||
"Kc9f2249c": "役割の設定",
|
||||
"Kba68dfc1": "API設定の追加",
|
||||
"Kc84dbd1a": "APIポータル",
|
||||
"Ke93388fd": "APIの編集",
|
||||
"K84aabfd4": "APIの追加",
|
||||
"K71ed51fa": "このサービスに先にサブスクリプションしてください",
|
||||
"K1bec8cbe": "APIキーを選択してください",
|
||||
"K5611e01e": "この消費者はすでに購読しています",
|
||||
"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,70 +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/*",
|
||||
"K508d8bf4": "集成地址",
|
||||
"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": "模型",
|
||||
@@ -851,5 +851,77 @@
|
||||
"Kce2fcdbf": "暂无权限",
|
||||
"K24f6a5b4": "自定义(空模板)",
|
||||
"Kea608112": "载入预置模板",
|
||||
"Kee7de862": "编辑供应商( (0) )"
|
||||
"Kee7de862": "编辑供应商( (0) )",
|
||||
"Kb0e0aeda": "新增 API Key",
|
||||
"K9d81999c": "API 密钥可用于调用系统级 Open API 和 MCP。",
|
||||
"Ka7ca8fde": "OpenAPI & MCP 调用地址",
|
||||
"K6e9c928f": "MCP 配置",
|
||||
"Kb6d0eb39": "Open API 文档",
|
||||
"Ke6908f16": "AI 代理集成",
|
||||
"Kf106bc62": "MCP 服务",
|
||||
"K7c2bfeff": "MCP Service 充当 AI 模型与 API 之间的桥梁,允许智能助手(如 Claude)动态发现和调用 Gateway 上的 API,无需繁琐的手动配置或自定义集成。",
|
||||
"K30595880": "关闭 MCP",
|
||||
"Kc081047c": "关闭后将无法通过MCP方式调用服务",
|
||||
"K373c8ab3": "MCP",
|
||||
"K3ba29a85": "API",
|
||||
"K3d52b756": "30天内调用次数",
|
||||
"Ke959f135": "开启:AI Agent 等产品能够通过 MCP 方式调用服务",
|
||||
"Ka73a5801": "了解",
|
||||
"K6cd677b": "说明文档",
|
||||
"Kec68fe24": "编辑 API 设置",
|
||||
"Kc9aeee51": "新增 API 设置",
|
||||
"Kf06f6737": "编辑服务策略",
|
||||
"K205971e1": "添加服务策略",
|
||||
"Kc9f2249c": "角色配置",
|
||||
"Kba68dfc1": "添加 API 设置",
|
||||
"Kc84dbd1a": "API 门户",
|
||||
"Ke93388fd": "编辑 API",
|
||||
"K84aabfd4": "添加 API",
|
||||
"K71ed51fa": "请先订阅该服务",
|
||||
"K1bec8cbe": "选择 API Key",
|
||||
"K5611e01e": "该消费者已订阅",
|
||||
"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": "模型",
|
||||
@@ -920,5 +920,79 @@
|
||||
"Kce2fcdbf": "暫無權限",
|
||||
"K24f6a5b4": "自訂(空模板)",
|
||||
"Kea608112": "載入預設模板",
|
||||
"Kee7de862": "編輯供應商( (0) )"
|
||||
"Kee7de862": "編輯供應商( (0) )",
|
||||
"Kb0e0aeda": "新增 API 金鑰",
|
||||
"K9d81999c": "API 金鑰可用於調用系統級 Open API 和 MCP。",
|
||||
"Ka7ca8fde": "OpenAPI & MCP 呼叫地址",
|
||||
"K6e9c928f": "MCP 設定",
|
||||
"Kb6d0eb39": "Open API 文件",
|
||||
"Ke6908f16": "AI 代理整合",
|
||||
"Kf106bc62": "MCP 服務",
|
||||
"K7c2bfeff": "MCP 服務作為 AI 模型與 API 之間的橋樑,允許智能助手(如 Claude)動態發現並呼叫 Gateway 上的 API,而無需繁瑣的手動設定或自訂整合。",
|
||||
"K30595880": "關閉 MCP",
|
||||
"Kc081047c": "關閉後將無法透過 MCP 方式呼叫服務",
|
||||
"K373c8ab3": "MCP",
|
||||
"K3ba29a85": "API",
|
||||
"K3d52b756": "30 天內呼叫次數",
|
||||
"Ke959f135": "開啟:AI Agent 等產品能夠透過 MCP 方式呼叫服務",
|
||||
"Ka73a5801": "了解",
|
||||
"K6cd677b": "說明文檔",
|
||||
"Kec68fe24": "編輯 API 設定",
|
||||
"Kc9aeee51": "新增 API 設定",
|
||||
"Kf06f6737": "編輯服務策略",
|
||||
"K205971e1": "新增服務策略",
|
||||
"Kc9f2249c": "角色配置",
|
||||
"Kba68dfc1": "新增 API 設定",
|
||||
"Kc84dbd1a": "API 門戶",
|
||||
"Ke93388fd": "編輯 API",
|
||||
"K84aabfd4": "新增 API",
|
||||
"K71ed51fa": "請先訂閱該服務",
|
||||
"K1bec8cbe": "選擇 API Key",
|
||||
"K5611e01e": "該消費者已訂閱",
|
||||
"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 消耗"
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -91,8 +91,15 @@ const AIProviderSelect: React.FC<AIProviderSelectProps> = ({ value, onChange, so
|
||||
label: (
|
||||
<Space className="flex items-center">
|
||||
<span
|
||||
className="flex items-center h-[20px] w-[20px]"
|
||||
dangerouslySetInnerHTML={{ __html: provider.logo }}
|
||||
className="flex items-center justify-center h-[20px] w-[20px] overflow-hidden"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: provider.logo.replace(/<svg/, '<svg style="max-width:100%;max-height:100%;width:auto;height:auto;"')
|
||||
}}
|
||||
></span>
|
||||
<span>{provider.name}</span>
|
||||
</Space>
|
||||
|
||||
@@ -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,12 +15,13 @@ export type AiServiceConfigFieldType = {
|
||||
logoFile?:UploadFile;
|
||||
tags?:Array<string>;
|
||||
description?: string;
|
||||
team?:string;
|
||||
team?:EntityItem;
|
||||
master?:string;
|
||||
serviceType?:'public'|'inner';
|
||||
catalogue?:string | string[];
|
||||
approvalType?:string;
|
||||
providerType?:string
|
||||
enable_mcp?: boolean
|
||||
};
|
||||
|
||||
export type AiServiceSubServiceTableListItem = {
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
@@ -800,6 +828,22 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
'mcpService',
|
||||
{
|
||||
type: 'module',
|
||||
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/mcpService/McpServiceContainer')),
|
||||
key: 'mcpService'
|
||||
}
|
||||
],
|
||||
[
|
||||
'mcpKey',
|
||||
{
|
||||
type: 'module',
|
||||
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/mcpService/McpKeyContainer')),
|
||||
key: 'mcpKey'
|
||||
}
|
||||
],
|
||||
[
|
||||
'loadBalancing',
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
@@ -85,7 +86,7 @@ export const SYSTEM_TABLE_COLUMNS: PageProColumns<SystemTableListItem>[] = [
|
||||
title: '服务名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true,
|
||||
width: 160,
|
||||
width: 220,
|
||||
fixed: 'left',
|
||||
sorter: (a, b) => {
|
||||
return a.name.localeCompare(b.name)
|
||||
@@ -100,24 +101,20 @@ export const SYSTEM_TABLE_COLUMNS: PageProColumns<SystemTableListItem>[] = [
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'service_kind',
|
||||
width: 140,
|
||||
width: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '所属团队',
|
||||
dataIndex: ['team', 'name'],
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
width: 140,
|
||||
dataIndex: 'state',
|
||||
ellipsis: true
|
||||
ellipsis: true,
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: 'API 数量',
|
||||
dataIndex: 'apiNum',
|
||||
ellipsis: true,
|
||||
width: 140,
|
||||
sorter: (a, b) => {
|
||||
return a.apiNum - b.apiNum
|
||||
}
|
||||
@@ -245,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',
|
||||
@@ -362,6 +365,10 @@ export const SERVICE_APPROVAL_OPTIONS = [
|
||||
{ label: '无需审核:允许任何消费者调用该服务', value: 'auto' },
|
||||
{ label: '人工审核:仅允许通过人工审核的消费者调用该服务', value: 'manual' }
|
||||
]
|
||||
export const MCP_OPTIONS = [
|
||||
{ label: '禁用', value: false },
|
||||
{ label: '开启:AI Agent 等产品能够通过 MCP 方式调用服务', value: true }
|
||||
]
|
||||
export const SERVICE_KIND_OPTIONS = [
|
||||
{ label: 'REST', value: 'rest' },
|
||||
{ label: 'AI', value: 'ai' }
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
@@ -31,6 +31,7 @@ export type SystemConfigFieldType = {
|
||||
catalogue?:string | string[];
|
||||
approvalType?:string;
|
||||
modelMapping?: string;
|
||||
enable_mcp?: boolean;
|
||||
};
|
||||
|
||||
export type SystemSubServiceTableListItem = {
|
||||
@@ -96,6 +97,7 @@ export type SystemApiProxyType = {
|
||||
export type SystemApiProxyFieldType = {
|
||||
protocols: string[];
|
||||
id:string;
|
||||
name:string
|
||||
description?:string;
|
||||
disable:boolean;
|
||||
path:string;
|
||||
|
||||
@@ -666,6 +666,15 @@ p{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.apipark-layout-base-menu-horizontal-menu-item {
|
||||
padding-right: 0px !important;
|
||||
padding-left: 0px !important;
|
||||
}
|
||||
.apipark-layout-base-menu-horizontal-item-title {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.ant-pro-table-list-toolbar-setting-items{
|
||||
position:absolute;
|
||||
top:18px;
|
||||
@@ -1100,6 +1109,11 @@ p{
|
||||
width: 16px !important;
|
||||
justify-content:center;
|
||||
}
|
||||
.ant-select .ant-select-clear {
|
||||
height:16px !important;
|
||||
width: 16px !important;
|
||||
top: 45%;
|
||||
}
|
||||
|
||||
.ant-table-wrapper .ant-table{
|
||||
scrollbar-color: none !important;
|
||||
@@ -1142,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;
|
||||
@@ -1204,6 +1241,38 @@ p{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 将折叠图标移到右侧 */
|
||||
.service-hub-custom-switcher .ant-tree-switcher {
|
||||
position: absolute !important;
|
||||
right: 0 !important;
|
||||
left: auto !important;
|
||||
z-index: 10 !important;
|
||||
}
|
||||
|
||||
/* 设置节点内容宽度 */
|
||||
.service-hub-custom-switcher .ant-tree-node-content-wrapper {
|
||||
width: calc(100% - 24px) !important;
|
||||
padding-left: 8px !important;
|
||||
padding-right: 24px !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 强制控制标题元素的样式 */
|
||||
.service-hub-custom-switcher .ant-tree-title {
|
||||
display: inline-block !important;
|
||||
width: calc(100% - 10px) !important;
|
||||
overflow: hidden !important;
|
||||
white-space: nowrap !important;
|
||||
text-overflow: ellipsis !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
/* 确保选中框的间距 */
|
||||
.service-hub-custom-switcher .ant-tree-checkbox {
|
||||
margin-right: 8px !important;
|
||||
}
|
||||
|
||||
|
||||
.no-selected-tree .ant-tree-node-selected{
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
@@ -1,145 +1,191 @@
|
||||
import { Editor } from '@tinymce/tinymce-react';
|
||||
import hljs from 'highlight.js';
|
||||
import 'highlight.js/styles/default.css';
|
||||
import {useEffect, useState} from "react";
|
||||
import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
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 { useParams } from 'react-router-dom';
|
||||
import { $t } from '@common/locales';
|
||||
const ServiceInsideDocument = ()=>{
|
||||
const { message } = App.useApp()
|
||||
const [updater,setUpdater] = useState<string>()
|
||||
const [updateTime,setUpdateTime]=useState<string>()
|
||||
const [initDoc, setInitDoc] = useState<string>()
|
||||
const [doc, setDoc] = useState<string>()
|
||||
const {fetchData} = useFetch()
|
||||
const { serviceId, teamId} = useParams<RouterParams>();
|
||||
import { Editor } from '@tinymce/tinymce-react'
|
||||
import hljs from 'highlight.js'
|
||||
import 'highlight.js/styles/default.css'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
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 { useParams } from 'react-router-dom'
|
||||
import { $t } from '@common/locales'
|
||||
const ServiceInsideDocument = () => {
|
||||
const { message } = App.useApp()
|
||||
const [updater, setUpdater] = useState<string>()
|
||||
const [updateTime, setUpdateTime] = useState<string>()
|
||||
const [initDoc, setInitDoc] = useState<string>()
|
||||
const [doc, setDoc] = useState<string>()
|
||||
const { fetchData } = useFetch()
|
||||
const { serviceId, teamId } = useParams<RouterParams>()
|
||||
|
||||
const save = ()=>{
|
||||
fetchData<BasicResponse<{service:{ id:string,name:string,updater:string,updateTime:string, doc:string} }>>('service/doc',{method:'PUT',eoBody:({doc:doc}) ,eoParams:{service:serviceId,team:teamId},eoTransformKeys:['update_time']}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
getServiceDoc()
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleEditorChange = (content:string, editor:unknown) => {
|
||||
setDoc(content)
|
||||
};
|
||||
const setupEditor = (editor:unknown) => {
|
||||
editor.on('init', () => {
|
||||
editor.contentDocument.querySelectorAll('pre code').forEach((block:HTMLElement) => {
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
});
|
||||
|
||||
editor.on('SetContent', () => {
|
||||
editor.contentDocument.querySelectorAll('pre code').forEach((block:HTMLElement) => {
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getServiceDoc = ()=>{
|
||||
fetchData<BasicResponse<{doc:{ id:string,name:string,updater:EntityItem,updateTime:string,creater:EntityItem, doc:string} }>>('service/doc',{method:'GET',eoParams:{service:serviceId,team:teamId},eoTransformKeys:['update_time']}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setUpdater(data.doc.updater.id === '' ? '-' : data.doc.updater.name)
|
||||
setUpdateTime(data.doc.updater.id === '' ? '-' : data.doc.updateTime)
|
||||
setInitDoc(data.doc.doc)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const save = () => {
|
||||
fetchData<
|
||||
BasicResponse<{ service: { id: string; name: string; updater: string; updateTime: string; doc: string } }>
|
||||
>('service/doc', {
|
||||
method: 'PUT',
|
||||
eoBody: { doc: doc },
|
||||
eoParams: { service: serviceId, team: teamId },
|
||||
eoTransformKeys: ['update_time']
|
||||
}).then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
getServiceDoc()
|
||||
}, []);
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full border-[1px] rounded-[10px] border-BORDER border-solid mr-PAGE_INSIDE_X">
|
||||
<Editor
|
||||
tinymceScriptSrc={'/tinymce/tinymce.min.js'}
|
||||
initialValue={initDoc}
|
||||
init={{
|
||||
height: '100%',
|
||||
menubar: false,
|
||||
plugins: [
|
||||
'advlist', 'autolink', 'link', 'image', 'lists', 'charmap', 'preview', 'anchor', 'pagebreak',
|
||||
'searchreplace', 'wordcount', 'visualblocks', 'visualchars', 'codesample', 'fullscreen', 'insertdatetime',
|
||||
'media', 'table', 'emoticons', 'help'
|
||||
], toolbar: 'undo redo | styles | bold italic | alignleft aligncenter alignright alignjustify | codesample |table|' +
|
||||
'bullist numlist outdent indent | link image | print preview media fullscreen | ' +
|
||||
'forecolor backcolor emoticons | help',
|
||||
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px; } img { max-width: 100%; }',
|
||||
setup: setupEditor,
|
||||
codesample_languages:[
|
||||
{
|
||||
text: 'HTML/XML',
|
||||
value: 'markup'
|
||||
},
|
||||
{
|
||||
text: 'JavaScript',
|
||||
value: 'javascript'
|
||||
},
|
||||
{
|
||||
text: 'CSS',
|
||||
value: 'css'
|
||||
},
|
||||
{
|
||||
text: 'PHP',
|
||||
value: 'php'
|
||||
},
|
||||
{
|
||||
text: 'Ruby',
|
||||
value: 'ruby'
|
||||
},
|
||||
{
|
||||
text:'GO',
|
||||
value:'go'
|
||||
},
|
||||
{
|
||||
text: 'Python',
|
||||
value: 'python'
|
||||
},
|
||||
{
|
||||
text: 'Java',
|
||||
value: 'java'
|
||||
},
|
||||
{
|
||||
text: 'C',
|
||||
value: 'c'
|
||||
},
|
||||
{
|
||||
text: 'C#',
|
||||
value: 'csharp'
|
||||
},
|
||||
{
|
||||
text: 'C++',
|
||||
value: 'cpp'
|
||||
},
|
||||
{ text: 'Bash/Shell', value: 'bash' },
|
||||
{ text: 'SQL', value: 'sql' }
|
||||
]
|
||||
}}
|
||||
onEditorChange={handleEditorChange}
|
||||
/>
|
||||
|
||||
<div className=" pl-[8px] py-btnbase ">
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="text-[14px] leading-[20px] text-[#999999]"><span className="mr-[20px]">{$t('最近一次更新者')}:{updater || '-'}</span><span>{$t('最近一次更新时间')}:{updateTime || '-'}</span></p>
|
||||
<WithPermission access="team.service.service_intro.edit"><Button type="primary" className="mr-btnbase" onClick={save}>{$t('保存')}</Button></WithPermission>
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
const handleEditorChange = (content: string, editor: unknown) => {
|
||||
setDoc(content)
|
||||
}
|
||||
const setupEditor = (editor: unknown) => {
|
||||
editor.on('init', () => {
|
||||
editor.contentDocument.querySelectorAll('pre code').forEach((block: HTMLElement) => {
|
||||
hljs.highlightBlock(block)
|
||||
})
|
||||
})
|
||||
|
||||
editor.on('SetContent', () => {
|
||||
editor.contentDocument.querySelectorAll('pre code').forEach((block: HTMLElement) => {
|
||||
hljs.highlightBlock(block)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getServiceDoc = () => {
|
||||
fetchData<
|
||||
BasicResponse<{
|
||||
doc: { id: string; name: string; updater: EntityItem; updateTime: string; creater: EntityItem; doc: string }
|
||||
}>
|
||||
>('service/doc', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId, team: teamId },
|
||||
eoTransformKeys: ['update_time']
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setUpdater(data.doc.updater.id === '' ? '-' : data.doc.updater.name)
|
||||
setUpdateTime(data.doc.updater.id === '' ? '-' : data.doc.updateTime)
|
||||
setInitDoc(data.doc.doc)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getServiceDoc()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full border-[1px] rounded-[10px] border-BORDER border-solid mr-PAGE_INSIDE_X">
|
||||
<Editor
|
||||
tinymceScriptSrc={'/tinymce/tinymce.min.js'}
|
||||
initialValue={initDoc}
|
||||
init={{
|
||||
height: '100%',
|
||||
menubar: false,
|
||||
plugins: [
|
||||
'advlist',
|
||||
'autolink',
|
||||
'link',
|
||||
'image',
|
||||
'lists',
|
||||
'charmap',
|
||||
'preview',
|
||||
'anchor',
|
||||
'pagebreak',
|
||||
'searchreplace',
|
||||
'wordcount',
|
||||
'visualblocks',
|
||||
'visualchars',
|
||||
'codesample',
|
||||
'fullscreen',
|
||||
'insertdatetime',
|
||||
'media',
|
||||
'table',
|
||||
'emoticons',
|
||||
'help'
|
||||
],
|
||||
toolbar:
|
||||
'undo redo | styles | bold italic | alignleft aligncenter alignright alignjustify | codesample |table|' +
|
||||
'bullist numlist outdent indent | link image | print preview media fullscreen | ' +
|
||||
'forecolor backcolor emoticons | help',
|
||||
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px; } img { max-width: 100%; }',
|
||||
setup: setupEditor,
|
||||
codesample_languages: [
|
||||
{
|
||||
text: 'HTML/XML',
|
||||
value: 'markup'
|
||||
},
|
||||
{
|
||||
text: 'JavaScript',
|
||||
value: 'javascript'
|
||||
},
|
||||
{
|
||||
text: 'CSS',
|
||||
value: 'css'
|
||||
},
|
||||
{
|
||||
text: 'PHP',
|
||||
value: 'php'
|
||||
},
|
||||
{
|
||||
text: 'Ruby',
|
||||
value: 'ruby'
|
||||
},
|
||||
{
|
||||
text: 'GO',
|
||||
value: 'go'
|
||||
},
|
||||
{
|
||||
text: 'Python',
|
||||
value: 'python'
|
||||
},
|
||||
{
|
||||
text: 'Java',
|
||||
value: 'java'
|
||||
},
|
||||
{
|
||||
text: 'C',
|
||||
value: 'c'
|
||||
},
|
||||
{
|
||||
text: 'C#',
|
||||
value: 'csharp'
|
||||
},
|
||||
{
|
||||
text: 'C++',
|
||||
value: 'cpp'
|
||||
},
|
||||
{ text: 'Bash/Shell', value: 'bash' },
|
||||
{ text: 'SQL', value: 'sql' }
|
||||
]
|
||||
}}
|
||||
onEditorChange={handleEditorChange}
|
||||
/>
|
||||
|
||||
<div className=" pl-[8px] py-btnbase ">
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="text-[14px] leading-[20px] text-[#999999]">
|
||||
<span className="mr-[20px]">
|
||||
{$t('最近一次更新者')}:{updater || '-'}
|
||||
</span>
|
||||
<span>
|
||||
{$t('最近一次更新时间')}:{updateTime || '-'}
|
||||
</span>
|
||||
</p>
|
||||
<WithPermission access="team.service.service_intro.edit">
|
||||
<Button type="primary" className="mr-btnbase" onClick={save}>
|
||||
{$t('保存')}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default ServiceInsideDocument
|
||||
export default ServiceInsideDocument
|
||||
|
||||
@@ -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,30 +217,35 @@ 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()
|
||||
}, [serviceId])
|
||||
// 创建一个回调函数
|
||||
const onSaveCallback = () => {
|
||||
getAiServiceInfo()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{showMenu ? (
|
||||
<InsidePage
|
||||
pageTitle={aiServiceInfo?.name || '-'}
|
||||
tagList={[
|
||||
{
|
||||
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
|
||||
@@ -250,7 +259,7 @@ const AiServiceInsidePage: FC = () => {
|
||||
<div
|
||||
className={` ${['setting', 'upstream'].indexOf(activeMenu!) !== -1 ? '' : ''} w-full h-full flex flex-1 flex-col overflow-auto bg-MAIN_BG pt-[20px] pl-[20px] pb-PAGE_INSIDE_B `}
|
||||
>
|
||||
<Outlet />
|
||||
<Outlet context={{ onSaveComplete: onSaveCallback }} />
|
||||
</div>
|
||||
</div>
|
||||
</InsidePage>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import {ActionType} from "@ant-design/pro-components";
|
||||
import {FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react";
|
||||
import {Link, 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>()
|
||||
@@ -119,14 +117,6 @@ const AiServiceInsideSubscriber:FC = ()=>{
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title:<Link to={`/service/list`}>{$t('服务')}</Link>
|
||||
},
|
||||
{
|
||||
title:$t('订阅方管理')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
@@ -1,58 +1,72 @@
|
||||
|
||||
import {forwardRef, useEffect, useState} from "react";
|
||||
import { Empty, Spin, message} from "antd";
|
||||
import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { forwardRef, useEffect, useState } from 'react'
|
||||
import { Empty, Spin, message } from 'antd'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import EmptySVG from '@common/assets/empty.svg'
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import ApiDocument from '@common/components/aoplatform/ApiDocument.tsx'
|
||||
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 { useParams } from 'react-router-dom'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import {
|
||||
AiServiceInsideApiDocumentHandle,
|
||||
AiServiceInsideApiDocumentProps,
|
||||
AiServiceApiDetail
|
||||
} from '@core/const/ai-service/type.ts'
|
||||
|
||||
const AiServiceInsideApiDocument = forwardRef<AiServiceInsideApiDocumentHandle,AiServiceInsideApiDocumentProps>(() => {
|
||||
const {serviceId, teamId} = useParams<RouterParams>()
|
||||
const {fetchData} = useFetch()
|
||||
const [apiDetail, setApiDetail] = useState<AiServiceApiDetail>()
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
useEffect(() => {
|
||||
getApiDetail()
|
||||
}, []);
|
||||
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 getApiDetail = ()=>{
|
||||
setLoading(true)
|
||||
fetchData<BasicResponse<{doc:AiServiceApiDetail}>>('service/api_doc',{method:'GET',eoParams:{service:serviceId,team:teamId },eoTransformKeys:['update_time']}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setApiDetail(data.doc?.content)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).finally(()=>{setLoading(false)})
|
||||
}
|
||||
useEffect(() => {
|
||||
getApiDetail()
|
||||
}, [])
|
||||
|
||||
const getApiDetail = () => {
|
||||
setLoading(true)
|
||||
fetchData<BasicResponse<{ doc: AiServiceApiDetail }>>('service/api_doc', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId, team: teamId },
|
||||
eoTransformKeys: ['update_time']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setApiDetail(data.doc?.content)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
const ApiPreview = ({spec}:{spec?:string | object})=>{
|
||||
return (
|
||||
<div className="h-full overflow-hidden">
|
||||
<div className="flex-1 h-full overflow-auto pr-PAGE_INSIDE_X">
|
||||
<ApiDocument spec={spec}/>
|
||||
</div>
|
||||
const ApiPreview = ({ spec }: { spec?: string | object }) => {
|
||||
return (
|
||||
<div className="h-full overflow-hidden">
|
||||
<div className="flex-1 h-full overflow-auto pr-PAGE_INSIDE_X">
|
||||
<ApiDocument spec={spec} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (<>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} wrapperClassName=' h-full overflow-hidden '>
|
||||
<div className=" h-full ai-service-api-preview">
|
||||
{ apiDetail ? <ApiPreview spec={apiDetail} />
|
||||
: <Empty image={EmptySVG} >
|
||||
</Empty>}
|
||||
</div>
|
||||
</Spin>
|
||||
</>)
|
||||
return (
|
||||
<>
|
||||
<Spin
|
||||
indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
|
||||
spinning={loading}
|
||||
wrapperClassName=" h-full overflow-hidden "
|
||||
>
|
||||
<div className=" h-full ai-service-api-preview">
|
||||
{apiDetail ? <ApiPreview spec={apiDetail} /> : <Empty image={EmptySVG}></Empty>}
|
||||
</div>
|
||||
</Spin>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
export default AiServiceInsideApiDocument
|
||||
export default AiServiceInsideApiDocument
|
||||
|
||||
@@ -21,6 +21,7 @@ import { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import AiServiceRouterModelConfig, { AiServiceRouterModelConfigHandle } from './AiServiceInsideRouterModelConfig'
|
||||
import { AiProviderDefaultConfig, AiProviderLlmsItems } from '@core/pages/aiSetting/types'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
|
||||
|
||||
type AiServiceRouterField = {
|
||||
name: string
|
||||
@@ -66,6 +67,7 @@ const AiServiceInsideRouterCreate = () => {
|
||||
const [variablesTableRef, setVariablesTableRef] = useState<MutableRefObject<EditableFormInstance<T> | undefined>>()
|
||||
const { state } = useGlobalContext()
|
||||
const [resultPath, setResultPath] = useState<string>('')
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
|
||||
const onFinish = () => {
|
||||
return variablesTableRef?.current
|
||||
@@ -363,11 +365,6 @@ const AiServiceInsideRouterCreate = () => {
|
||||
setDrawerType(undefined)
|
||||
}
|
||||
|
||||
const apiPathMatchRulesOptions = useMemo(
|
||||
() => API_PATH_MATCH_RULES.map((x) => ({ label: $t(x.label), value: x.value })),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
return (
|
||||
<InsidePage
|
||||
pageTitle={$t('AI 路由设置') || '-'}
|
||||
|
||||
@@ -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)
|
||||
@@ -159,14 +157,6 @@ const AiServiceInsideRouterList: FC = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: <Link to={`/service/list`}>{$t('服务')}</Link>
|
||||
},
|
||||
{
|
||||
title: $t('路由')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId])
|
||||
|
||||
@@ -10,7 +10,7 @@ import { DefaultOptionType } from 'antd/es/select'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
|
||||
|
||||
export type AiServiceRouterModelConfigHandle = {
|
||||
save: () => Promise<{ id: string; config: string, type: string, provider: string }>
|
||||
save: () => Promise<{ id: string; config: string; type: string; provider: string }>
|
||||
}
|
||||
|
||||
export type AiServiceRouterModelConfigProps = {
|
||||
@@ -49,7 +49,7 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
|
||||
/**
|
||||
* 获取本地模型列表
|
||||
* @param setDefaultValue
|
||||
* @param setDefaultValue
|
||||
*/
|
||||
const getLocalLlmList = (setDefaultValue?: boolean) => {
|
||||
fetchData<LocalLlmType[]>('simple/ai/models/local/configured', {
|
||||
@@ -113,12 +113,11 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setProviderList(
|
||||
data.providers
|
||||
?.map((x: SimpleAiProviderItem) => {
|
||||
return { ...x, label: x.name, value: x.id }
|
||||
})
|
||||
data.providers?.map((x: SimpleAiProviderItem) => {
|
||||
return { ...x, label: x.name, value: x.id }
|
||||
})
|
||||
)
|
||||
if (setDefaultValue && data.providers.length) {
|
||||
if (setDefaultValue && data.providers.length) {
|
||||
const id = data.providers[0].id
|
||||
form.setFieldValue('provider', id)
|
||||
getLlmList(id)
|
||||
@@ -164,6 +163,8 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
>
|
||||
<Form.Item<AiServiceRouterModelConfigField> label={$t('模型类型')} name="type" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={modelTypeList}
|
||||
@@ -179,9 +180,14 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
className="w-INPUT_NORMAL"
|
||||
filterOption={(input, option) => (option?.searchText ?? '').includes(input.toLowerCase())}
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={providerList}
|
||||
options={providerList.map((x) => ({
|
||||
...x,
|
||||
searchText: x.name.toLowerCase()
|
||||
}))}
|
||||
onChange={(e) => {
|
||||
getLlmList(e)
|
||||
}}
|
||||
@@ -191,19 +197,20 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
|
||||
|
||||
<Form.Item<AiServiceRouterModelConfigField> label={$t('模型')} name="id" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={
|
||||
llmList?.map((x) => ({
|
||||
value: x.id,
|
||||
label: (
|
||||
<div className="flex items-center gap-[10px]" key={x.id}>
|
||||
<span>{x.name || x.id}</span>
|
||||
{modelType === 'online' && x?.scopes?.map((s: any) => <Tag>{s?.toLocaleUpperCase()}</Tag>)}
|
||||
</div>
|
||||
)
|
||||
}))
|
||||
}
|
||||
filterOption={(input, option) => (option?.searchText ?? '').includes(input.toLowerCase())}
|
||||
options={llmList?.map((x) => ({
|
||||
value: x.id,
|
||||
label: (
|
||||
<div className="flex items-center gap-[10px]" key={x.id}>
|
||||
<span>{x.name || x.id}</span>
|
||||
{modelType === 'online' && x?.scopes?.map((s: any) => <Tag>{s?.toLocaleUpperCase()}</Tag>)}
|
||||
</div>
|
||||
),
|
||||
searchText: x.name.toLowerCase()
|
||||
}))}
|
||||
onChange={(e) => {
|
||||
form.setFieldValue('config', llmList.find((x) => x.id === e)?.config)
|
||||
}}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
|
||||
import {ActionType} from "@ant-design/pro-components";
|
||||
import {FC, useEffect, useMemo, useRef, useState} from "react";
|
||||
import {Link, useLocation, useParams} from "react-router-dom";
|
||||
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)
|
||||
@@ -141,14 +139,6 @@ const AiServiceInsideApprovalList:FC = ()=>{
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title:<Link to={`/service/list`}>{$t('服务')}</Link>
|
||||
},
|
||||
{
|
||||
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,17 +23,6 @@ const AiServiceInsidePublic:FC = ()=>{
|
||||
setPageStatus(Number(query.get('status') ||0) as 0|1)
|
||||
}, [currentUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title:<Link to={`/service/list`}>{$t('服务')}</Link>
|
||||
},
|
||||
{
|
||||
title:$t('发布')
|
||||
}
|
||||
])
|
||||
}, []);
|
||||
|
||||
const tabItems = useMemo(()=>SYSTEM_PUBLISH_TAB_ITEMS?.map((x)=>({...x, label:$t(x.label as string) })),[state.language])
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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 } from "react-router-dom";
|
||||
import { useParams, Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList";
|
||||
import { PublishApprovalModalContent } from "@common/components/aoplatform/PublishApprovalModalContent";
|
||||
import { RouterParams } from "@core/components/aoplatform/RenderRoutes";
|
||||
@@ -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);
|
||||
@@ -350,14 +348,6 @@ const AiServiceInsidePublicList:FC = ()=>{
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title:<Link to={`/service/list`}>{$t('服务')}</Link>
|
||||
},
|
||||
{
|
||||
title:$t('发布')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
@@ -283,8 +283,9 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
|
||||
),
|
||||
onOk: () => {
|
||||
return addModelModalRef.current?.save().then((res) => {
|
||||
if (res === true) {
|
||||
if (res) {
|
||||
getLlmList(lastLlmID)
|
||||
form.setFieldValue('defaultLlm', res)
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -389,6 +390,8 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
|
||||
{source === 'guide' && (
|
||||
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={teamList}
|
||||
|
||||
@@ -102,7 +102,7 @@ const AddModels = forwardRef<addModelsContentHandle, addModelContentProps>((prop
|
||||
...value,
|
||||
id: modelID
|
||||
}
|
||||
fetchData<BasicResponse<null>>('ai/provider/model', {
|
||||
fetchData<BasicResponse<{ model: { id: string, name: string } }>>('ai/provider/model', {
|
||||
method: type === 'edit' ? 'PUT' : 'POST',
|
||||
eoParams: { provider: providerID },
|
||||
eoBody: finalValue,
|
||||
@@ -112,7 +112,8 @@ const AddModels = forwardRef<addModelsContentHandle, addModelContentProps>((prop
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success($t(RESPONSE_TIPS.success) || msg)
|
||||
resolve(true)
|
||||
const llmId = response.data?.model?.id
|
||||
resolve(llmId)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
|
||||
@@ -86,9 +86,9 @@ export default function ApiRequestSetting() {
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<ApiRequestSettingFieldType>
|
||||
label={$t('集成地址')}
|
||||
label={$t('OpenAPI & MCP 调用地址')}
|
||||
name="sitePrefix"
|
||||
rules={[{ whitespace: true }]}
|
||||
rules={[{ required: true, whitespace: true }]}
|
||||
extra={$t('与外部平台集成时,获取 API 市场中文档信息的域名')}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
|
||||
@@ -181,6 +181,8 @@ const LocalAiDeploy = forwardRef<LocalAiDeployHandle, any>((props: any, ref: any
|
||||
</Form.Item>
|
||||
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={teamList}
|
||||
|
||||
@@ -106,6 +106,8 @@ const RestAIDeploy = forwardRef<RestAIDeployHandle, any>((props: any, ref: any)
|
||||
</Form.Item>
|
||||
<Form.Item label={$t('所属团队')} name="team" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={teamList}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -171,6 +171,8 @@ const AddLoadBalancingModel = forwardRef<LoadBalancingHandle>((props, ref: any)
|
||||
>
|
||||
<Form.Item<LoadModelDetailData> label={$t('模型类型')} name="type" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={modelTypeList}
|
||||
@@ -182,7 +184,9 @@ const AddLoadBalancingModel = forwardRef<LoadBalancingHandle>((props, ref: any)
|
||||
{modelType === 'online' && (
|
||||
<Form.Item<LoadModelDetailData> label={$t('模型供应商')} name="provider" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
className="w-INPUT_NORMAL"
|
||||
filterOption={(input, option) => (option?.searchText ?? '').includes(input.toLowerCase())}
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
loading={modelProviderLoading}
|
||||
options={modelProviderData?.map((x) => ({
|
||||
@@ -191,7 +195,8 @@ const AddLoadBalancingModel = forwardRef<LoadBalancingHandle>((props, ref: any)
|
||||
<div className="flex items-center gap-[10px]">
|
||||
<span>{x.name}</span>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
searchText: x.name.toLowerCase()
|
||||
}))}
|
||||
onChange={(e) => {
|
||||
modelProviderChange(e)
|
||||
@@ -201,8 +206,10 @@ const AddLoadBalancingModel = forwardRef<LoadBalancingHandle>((props, ref: any)
|
||||
)}
|
||||
<Form.Item label={$t('模型')} name="model" className="mt-[16px]" rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
filterOption={(input, option) => (option?.searchText ?? '').includes(input.toLowerCase())}
|
||||
loading={llmListLoading}
|
||||
options={
|
||||
llmList?.map((x) => ({
|
||||
@@ -212,7 +219,8 @@ const AddLoadBalancingModel = forwardRef<LoadBalancingHandle>((props, ref: any)
|
||||
<span>{x.name || x.id}</span>
|
||||
{ modelType === 'online' &&x?.scopes?.map((s: any) => <Tag key={s}>{s?.toLocaleUpperCase()}</Tag>)}
|
||||
</div>
|
||||
)
|
||||
),
|
||||
searchText: x.name.toLowerCase()
|
||||
}))
|
||||
}
|
||||
onChange={(value) => {
|
||||
|
||||
@@ -40,7 +40,7 @@ const LogSettings = () => {
|
||||
const menuData = useMemo(() => {
|
||||
const newMenu = menuItems?.map((x: DynamicMenuItem) => {
|
||||
return getItem(
|
||||
<Link to={`template/${x.name}`}>{$t(x.title)}</Link>,
|
||||
<Link to={`/logsettings/template/${x.name}`}>{$t(x.title)}</Link>,
|
||||
x.name,
|
||||
undefined,
|
||||
undefined,
|
||||
@@ -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">
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { App, Form, Input } from 'antd'
|
||||
import { $t } from '@common/locales'
|
||||
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle } from 'react'
|
||||
type modelFieldType = {
|
||||
name: string
|
||||
type: string
|
||||
model_parameters: string
|
||||
access_configuration: string
|
||||
}
|
||||
|
||||
export type addMcpKeysHandle = {
|
||||
save: () => Promise<boolean | string>
|
||||
}
|
||||
|
||||
type addMcpKeysProps = {
|
||||
name?: string
|
||||
value?: string
|
||||
type?: string
|
||||
apikey?: string
|
||||
}
|
||||
|
||||
const AddMcpKey = forwardRef<addMcpKeysHandle, addMcpKeysProps>((props, ref) => {
|
||||
const { name = '', value: editValue = '', type = 'new', apikey = '' } = props
|
||||
const [form] = Form.useForm()
|
||||
const { message } = App.useApp()
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
name,
|
||||
value: editValue
|
||||
})
|
||||
}, [])
|
||||
/**
|
||||
* 保存
|
||||
* @returns
|
||||
*/
|
||||
const save: () => Promise<boolean | string> = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
console.log('value', value)
|
||||
const finalValue = {
|
||||
...value,
|
||||
value: editValue ? editValue : uuidv4(),
|
||||
expired: 0
|
||||
}
|
||||
fetchData<BasicResponse<any>>('system/apikey', {
|
||||
method: type === 'new' ? 'POST' : 'PUT',
|
||||
eoBody: finalValue,
|
||||
...(type === 'edit' ? {
|
||||
eoParams: { apikey }
|
||||
} : {})
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success($t(RESPONSE_TIPS.success) || msg)
|
||||
resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
save
|
||||
}))
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
labelAlign="left"
|
||||
scrollToFirstError
|
||||
className="flex flex-col mx-auto h-full"
|
||||
name="mcpKeyModalConfig"
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item<modelFieldType> label={$t('名称')} name="name" rules={[{ required: true }]}>
|
||||
<Input autoFocus className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
)
|
||||
})
|
||||
|
||||
export default AddMcpKey
|
||||
@@ -0,0 +1,601 @@
|
||||
import { App, Button, Card, CascaderProps, Empty, Select } from 'antd'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||
import ReactJson from 'react-json-view'
|
||||
import { IconButton } from '@common/components/postcat/api/IconButton'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { useConnection } from './hook/useConnection'
|
||||
import { ClientRequest, Tool, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { z } from 'zod'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { ServiceDetailType } from '@market/const/serviceHub/type'
|
||||
import useCopyToClipboard from '@common/hooks/copy'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { Cascader } from 'antd/lib'
|
||||
|
||||
type ConfigList = {
|
||||
openApi?: {
|
||||
title: string
|
||||
configContent: string
|
||||
apiKeys: string[]
|
||||
}
|
||||
mcp: {
|
||||
title: string
|
||||
configContent: string
|
||||
apiKeys: string[]
|
||||
}
|
||||
}
|
||||
|
||||
type ApiKeyItem = {
|
||||
expired: number
|
||||
id: string
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
interface Option {
|
||||
value: string
|
||||
label: string
|
||||
children?: Option[]
|
||||
}
|
||||
|
||||
type ServiceApiKeyList = {
|
||||
id: string
|
||||
name: string
|
||||
apikeys: Array<{
|
||||
id: string
|
||||
name: string
|
||||
value: string
|
||||
expired: number
|
||||
}>
|
||||
}
|
||||
export interface IntegrationAIContainerRef {
|
||||
getServiceKeysList: () => void;
|
||||
}
|
||||
export interface IntegrationAIContainerProps {
|
||||
type: 'global' | 'service'
|
||||
handleToolsChange: (value: Tool[]) => void
|
||||
customClassName?: string
|
||||
service?: ServiceDetailType
|
||||
serviceId?: string
|
||||
currentTab?: string
|
||||
openModal?: (type: 'apply') => void
|
||||
}
|
||||
export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, IntegrationAIContainerProps>(
|
||||
({
|
||||
type,
|
||||
handleToolsChange,
|
||||
customClassName,
|
||||
service,
|
||||
serviceId,
|
||||
currentTab,
|
||||
openModal
|
||||
}: IntegrationAIContainerProps, ref) => {
|
||||
/** 当前激活的标签 */
|
||||
const [activeTab, setActiveTab] = useState(type === 'service' ? 'openApi' : 'mcp')
|
||||
/** 弹窗组件 */
|
||||
const { message } = App.useApp()
|
||||
/** 配置内容 */
|
||||
const [configContent, setConfigContent] = useState<string>('')
|
||||
/** 当前选中 API Key */
|
||||
const [apiKey, setApiKey] = useState<string>('')
|
||||
/** API Key 列表 */
|
||||
const [apiKeyList, setApiKeyList] = useState<any[]>([])
|
||||
/** Cascader Key 列表 */
|
||||
const [cascaderKeyList, setCascaderKeyList] = useState<string[]>([])
|
||||
/** MCP 服务器地址 */
|
||||
const [mcpServerUrl, setMcpServerUrl] = useState<string>('')
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
const navigator = useNavigate()
|
||||
/** 复制组件 */
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
/** 错误提示 */
|
||||
const [errors, setErrors] = useState<Record<string, string | null>>({
|
||||
resources: null,
|
||||
prompts: null,
|
||||
tools: null
|
||||
})
|
||||
/** 标签内容 */
|
||||
const [tabContent, setTabContent] = useState<ConfigList>({
|
||||
mcp: {
|
||||
title: $t('MCP 配置'),
|
||||
configContent: '',
|
||||
apiKeys: []
|
||||
}
|
||||
})
|
||||
/** HTTP 请求 */
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
/**
|
||||
* 初始化标签数据
|
||||
*/
|
||||
const initTabsData = () => {
|
||||
const params: ConfigList = {
|
||||
mcp: {
|
||||
title: $t('MCP 配置'),
|
||||
configContent: service?.mcpAccessConfig || '',
|
||||
apiKeys: []
|
||||
}
|
||||
}
|
||||
if (type === 'service') {
|
||||
params.openApi = {
|
||||
title: $t('Open API 文档'),
|
||||
configContent: service?.openapiAddress || '',
|
||||
apiKeys: []
|
||||
}
|
||||
}
|
||||
setTabContent(params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
const handleCopy = async (value: string): Promise<void> => {
|
||||
if (value) {
|
||||
copyToClipboard(value)
|
||||
message.success($t(RESPONSE_TIPS.copySuccess))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择 API Key
|
||||
* @param value
|
||||
*/
|
||||
const handleSelectChange = (value: string) => {
|
||||
setApiKey(value)
|
||||
}
|
||||
/**
|
||||
* Cascader 选择
|
||||
* @param value
|
||||
*/
|
||||
const handleCascaderChange: CascaderProps<Option>['onChange'] = (value) => {
|
||||
setApiKey(value.at(-1) || '')
|
||||
setCascaderKeyList(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局 MCP 配置
|
||||
* @returns
|
||||
*/
|
||||
const getGlobalMcpConfig = () => {
|
||||
fetchData<BasicResponse<null>>('global/mcp/config', {
|
||||
method: 'GET'
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setTabContent((prevTabContent) => ({
|
||||
...prevTabContent,
|
||||
mcp: {
|
||||
...prevTabContent.mcp,
|
||||
configContent: data.config || ''
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局 MCP 跳转
|
||||
*/
|
||||
const addKey = () => {
|
||||
navigator('/mcpKey')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局 API Key 列表
|
||||
*/
|
||||
const getGlobalKeysList = () => {
|
||||
fetchData<BasicResponse<null>>('simple/system/apikeys', {
|
||||
method: 'GET'
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
if (data.apikeys && data.apikeys.length > 0) {
|
||||
setApiKeyList(
|
||||
data.apikeys.map((item: ApiKeyItem) => {
|
||||
return {
|
||||
label: item.name,
|
||||
value: item.value
|
||||
}
|
||||
})
|
||||
)
|
||||
setApiKey(data.apikeys[0].value)
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出获取服务 API Key 列表
|
||||
*/
|
||||
useImperativeHandle(ref, () => ({
|
||||
getServiceKeysList
|
||||
}))
|
||||
|
||||
/**
|
||||
* 获取服务 API Key 列表
|
||||
*/
|
||||
const getServiceKeysList = () => {
|
||||
fetchData<BasicResponse<null>>(`my/app/apikeys`, {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId }
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
if (data.apps && data.apps.length > 0) {
|
||||
// 转换数据结构为 Cascader 所需格式
|
||||
const transformedData = data.apps.map((app: ServiceApiKeyList) => ({
|
||||
value: app.id,
|
||||
label: app.name,
|
||||
children: app.apikeys.map((key) => ({
|
||||
...key,
|
||||
label: key.name
|
||||
}))
|
||||
}))
|
||||
setApiKeyList(transformedData)
|
||||
if (data.apps[0].apikeys?.length) {
|
||||
setApiKey(data.apps[0].apikeys[0].value)
|
||||
setCascaderKeyList([data.apps[0].id, data.apps[0].apikeys[0].value])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除错误提示
|
||||
*/
|
||||
const clearError = (tabKey: keyof typeof errors) => {
|
||||
setErrors((prev) => ({ ...prev, [tabKey]: null }))
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求
|
||||
*/
|
||||
const makeRequest = async <T extends z.ZodType>(request: ClientRequest, schema: T, tabKey?: keyof typeof errors) => {
|
||||
try {
|
||||
const response = await makeConnectionRequest(request, schema)
|
||||
if (tabKey !== undefined) {
|
||||
clearError(tabKey)
|
||||
}
|
||||
return response
|
||||
} catch (e) {
|
||||
const errorString = (e as Error).message ?? String(e)
|
||||
if (tabKey !== undefined) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
[tabKey]: errorString
|
||||
}))
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 MCP 的 tools
|
||||
*/
|
||||
const listTools = async () => {
|
||||
const response = await makeRequest(
|
||||
{
|
||||
method: 'tools/list' as const,
|
||||
params: {}
|
||||
},
|
||||
ListToolsResultSchema,
|
||||
'tools'
|
||||
)
|
||||
handleToolsChange(response.tools)
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化连接 mcp
|
||||
*/
|
||||
const {
|
||||
connectionStatus,
|
||||
serverCapabilities,
|
||||
mcpClient,
|
||||
requestHistory,
|
||||
makeRequest: makeConnectionRequest,
|
||||
sendNotification,
|
||||
handleCompletion,
|
||||
completionsSupported,
|
||||
connect: connectMcpServer,
|
||||
disconnect: disconnectMcpServer
|
||||
} = useConnection({
|
||||
transportType: 'sse',
|
||||
sseUrl: '',
|
||||
proxyServerUrl: mcpServerUrl,
|
||||
requestTimeout: 1000
|
||||
})
|
||||
// 使用 useRef 保存最新的连接状态和断开函数
|
||||
const connectionStatusRef = useRef(connectionStatus)
|
||||
const disconnectFnRef = useRef(disconnectMcpServer)
|
||||
|
||||
// 当连接状态或断开函数变化时更新 ref
|
||||
useEffect(() => {
|
||||
connectionStatusRef.current = connectionStatus
|
||||
disconnectFnRef.current = disconnectMcpServer
|
||||
}, [connectionStatus, disconnectMcpServer])
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
const setupComponent = () => {
|
||||
initTabsData()
|
||||
if (type === 'global') {
|
||||
getGlobalMcpConfig()
|
||||
setMcpServerUrl('mcp/global/sse')
|
||||
getGlobalKeysList()
|
||||
} else {
|
||||
service?.basic.enableMcp && setMcpServerUrl(`mcp/service/${serviceId}/sse`)
|
||||
getServiceKeysList()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
useEffect(() => {
|
||||
setupComponent()
|
||||
}, [service])
|
||||
/**
|
||||
* 初始化标签数据
|
||||
*/
|
||||
useEffect(() => {
|
||||
initTabsData()
|
||||
type === 'global' && getGlobalMcpConfig()
|
||||
}, [state.language])
|
||||
/**
|
||||
* 切换标签
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (type === 'service') {
|
||||
currentTab === 'MCP' ? setActiveTab('mcp') : setActiveTab('openApi')
|
||||
}
|
||||
}, [currentTab])
|
||||
/**
|
||||
* 仅在组件加载时执行初始化逻辑
|
||||
*/
|
||||
useEffect(() => {
|
||||
// 返回清理函数,只会在组件卸载时执行
|
||||
return () => {
|
||||
try {
|
||||
// 使用 ref 中保存的最新函数强制断开连接
|
||||
const disconnectFn = disconnectFnRef.current
|
||||
if (disconnectFn) {
|
||||
disconnectFn()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('断开连接时出错:', err)
|
||||
}
|
||||
}
|
||||
}, [type])
|
||||
/**
|
||||
* 切换标签时更新配置内容
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (activeTab === 'openApi' && tabContent?.openApi?.configContent) {
|
||||
setConfigContent(tabContent?.openApi?.configContent)
|
||||
} else if (activeTab === 'mcp' && tabContent?.mcp?.configContent) {
|
||||
setConfigContent(tabContent.mcp.configContent?.replace('{your_api_key}', apiKey || '{your_api_key}'))
|
||||
}
|
||||
}, [service, apiKey, activeTab, tabContent])
|
||||
/**
|
||||
* 连接 MCP 服务器
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (mcpServerUrl) {
|
||||
if (connectionStatus === 'connected') {
|
||||
disconnectMcpServer()
|
||||
}
|
||||
connectMcpServer()
|
||||
}
|
||||
}, [mcpServerUrl, ...(type === 'global' ? [state.language] : [])])
|
||||
/**
|
||||
* 获取 MCP tools
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (connectionStatus === 'connected') {
|
||||
listTools()
|
||||
}
|
||||
}, [connectionStatus])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
style={{ borderRadius: '10px' }}
|
||||
className={`w-[400px] h-fit ${customClassName}`}
|
||||
classNames={{
|
||||
body: 'p-[10px]'
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
<Icon
|
||||
icon="icon-park-solid:connection-point-two"
|
||||
className="align-text-bottom mr-[5px]"
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
{$t('AI 代理集成')}
|
||||
</p>
|
||||
{type === 'service' && service?.basic.enableMcp && (
|
||||
<div className="mt-3 tab-nav flex rounded-md overflow-hidden border border-solid border-[#3D46F2] w-fit">
|
||||
<div
|
||||
className={`tab-item px-5 py-1.5 cursor-pointer text-sm transition-colors ${activeTab === 'openApi' ? 'bg-[#3D46F2] text-white' : 'bg-white text-[#3D46F2]'}`}
|
||||
onClick={() => setActiveTab('openApi')}
|
||||
>
|
||||
Open API
|
||||
</div>
|
||||
<div
|
||||
className={`tab-item px-5 py-1.5 cursor-pointer text-sm transition-colors ${activeTab === 'mcp' ? 'bg-[#3D46F2] text-white' : 'bg-white text-[#3D46F2]'}`}
|
||||
onClick={() => setActiveTab('mcp')}
|
||||
>
|
||||
MCP
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{type === 'service' && !apiKeyList.length ? (
|
||||
<>
|
||||
<Card
|
||||
style={{ borderRadius: '10px' }}
|
||||
className={`w-full mt-3`}
|
||||
classNames={{
|
||||
body: 'p-[10px]'
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center py-3">
|
||||
<span className="text-[14px] mb-5">{$t('请先订阅该服务')}</span>
|
||||
<Button type="primary" onClick={() => openModal?.('apply')}>
|
||||
{$t('申请')}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="tab-container mt-3">
|
||||
<div className="tab-content font-semibold mt-[10px]">
|
||||
{activeTab === 'openApi' ? tabContent.openApi?.title : tabContent.mcp.title}
|
||||
</div>
|
||||
{/* 标签页内容区域 */}
|
||||
<div className="bg-[#0a0b21] text-white p-4 rounded-md my-2 font-mono text-sm overflow-auto relative">
|
||||
{activeTab === 'mcp' ? (
|
||||
<ReactJson
|
||||
src={
|
||||
configContent
|
||||
? typeof configContent === 'string'
|
||||
? (() => {
|
||||
try {
|
||||
return JSON.parse(configContent)
|
||||
} catch (e) {
|
||||
return {}
|
||||
}
|
||||
})()
|
||||
: configContent
|
||||
: {}
|
||||
}
|
||||
theme="monokai"
|
||||
indentWidth={2}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
name={false}
|
||||
collapsed={false}
|
||||
enableClipboard={false}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<pre className="whitespace-pre-wrap break-words">{configContent || ''}</pre>
|
||||
</>
|
||||
)}
|
||||
<IconButton
|
||||
name="copy"
|
||||
onClick={() => handleCopy(configContent)}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '5px',
|
||||
right: '5px',
|
||||
color: '#999',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
</div>
|
||||
{activeTab === 'mcp' && (
|
||||
<>
|
||||
<div className="tab-content font-semibold my-[10px]">API Key</div>
|
||||
{apiKeyList.length ? (
|
||||
<>
|
||||
{type === 'global' ? (
|
||||
<>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
value={apiKey}
|
||||
className="w-full"
|
||||
onChange={handleSelectChange}
|
||||
options={apiKeyList}
|
||||
/>
|
||||
<Card
|
||||
style={{ borderRadius: '5px' }}
|
||||
className="w-full mt-[5px] "
|
||||
classNames={{
|
||||
body: 'p-[5px]'
|
||||
}}
|
||||
>
|
||||
<div className="relative h-[25px]">
|
||||
{apiKey}
|
||||
<IconButton
|
||||
name="copy"
|
||||
onClick={() => handleCopy(apiKey)}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
right: '5px',
|
||||
color: '#999',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Cascader
|
||||
className='w-full'
|
||||
allowClear={false}
|
||||
options={apiKeyList}
|
||||
value={cascaderKeyList}
|
||||
onChange={handleCascaderChange}
|
||||
placeholder={$t('选择 API Key')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={''}>
|
||||
<Button onClick={addKey} type="primary">
|
||||
{$t('新增 API Key')}
|
||||
</Button>
|
||||
</Empty>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,209 @@
|
||||
import InsidePage from '@common/components/aoplatform/InsidePage'
|
||||
import { IconButton } from '@common/components/postcat/api/IconButton'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { Button, Card, App, Empty } from 'antd'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import AddMcpKey, { addMcpKeysHandle } from './AddMcpKey'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
const McpKeyContainer = () => {
|
||||
const { fetchData } = useFetch()
|
||||
const { message, modal } = App.useApp()
|
||||
const [keys, setKeys] = useState<any[]>([])
|
||||
const [, forceUpdate] = useState<unknown>(null)
|
||||
const { state } = useGlobalContext()
|
||||
const addMcpKeyModalRef = useRef<addMcpKeysHandle>(null)
|
||||
|
||||
/**
|
||||
* 新增 API Key
|
||||
*/
|
||||
const addKey = () => {
|
||||
modal.confirm({
|
||||
title: $t('新增 API Key'),
|
||||
content: <AddMcpKey ref={addMcpKeyModalRef}></AddMcpKey>,
|
||||
onOk: () => {
|
||||
return addMcpKeyModalRef.current?.save().then((res) => {
|
||||
if (res) {
|
||||
getKeysList()
|
||||
}
|
||||
})
|
||||
},
|
||||
width: 600,
|
||||
okText: $t('确认'),
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
icon: <></>
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 API Key 列表
|
||||
*/
|
||||
const getKeysList = () => {
|
||||
fetchData<BasicResponse<null>>('system/apikeys', {
|
||||
method: 'GET'
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setKeys(data.apikeys || [])
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制 API Key
|
||||
*/
|
||||
const copyCode = async (value: string): Promise<void> => {
|
||||
if (value) {
|
||||
await navigator.clipboard.writeText(value)
|
||||
message.success($t(RESPONSE_TIPS.copySuccess))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 API Key
|
||||
*/
|
||||
const deleteKey = (id: string) => {
|
||||
modal.confirm({
|
||||
title: $t('删除'),
|
||||
content: $t('确定删除吗?'),
|
||||
onOk: async () => {
|
||||
try {
|
||||
const response = await fetchData<BasicResponse<'success'>>('system/apikey', {
|
||||
method: 'DELETE',
|
||||
eoParams: { apikey: id }
|
||||
})
|
||||
if (response.code === STATUS_CODE.SUCCESS) {
|
||||
message.success($t('删除成功'))
|
||||
getKeysList()
|
||||
}
|
||||
} catch (error) {
|
||||
message.error($t('删除失败'))
|
||||
}
|
||||
},
|
||||
width: 600,
|
||||
okText: $t('确认'),
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
icon: <></>
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑 API Key
|
||||
*/
|
||||
const editKey = (key: any) => {
|
||||
modal.confirm({
|
||||
title: $t('编辑'),
|
||||
content: (
|
||||
<AddMcpKey ref={addMcpKeyModalRef} name={key.name} value={key.value} apikey={key.id} type={'edit'}></AddMcpKey>
|
||||
),
|
||||
onOk: () => {
|
||||
return addMcpKeyModalRef.current?.save().then((res) => {
|
||||
if (res) {
|
||||
getKeysList()
|
||||
}
|
||||
})
|
||||
},
|
||||
width: 600,
|
||||
okText: $t('确认'),
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
icon: <></>
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getKeysList()
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
forceUpdate({})
|
||||
}, [state.language])
|
||||
return (
|
||||
<>
|
||||
<InsidePage
|
||||
pageTitle={$t('API Key')}
|
||||
description={$t('API 密钥可用于调用系统级 Open API 和 MCP。')}
|
||||
showBorder={false}
|
||||
scrollPage={false}
|
||||
>
|
||||
<Button type="primary" onClick={addKey}>
|
||||
{$t('新增 API Key')}
|
||||
</Button>
|
||||
<div className="api-key-container mt-[20px]">
|
||||
{keys.length ? (
|
||||
keys.map((key, index) => (
|
||||
<Card style={{ width: 600, borderRadius: '10px' }} key={index} className="mt-[10px]">
|
||||
<div className="flex">
|
||||
<div className="flex-1">
|
||||
<p className="text-[14px] font-bold">{key.name}</p>
|
||||
<div className="flex">
|
||||
<span className="h-[26px] leading-[28px]">{key.value}</span>
|
||||
<IconButton
|
||||
name="copy"
|
||||
onClick={() => {
|
||||
copyCode(key?.value)
|
||||
}}
|
||||
sx={{
|
||||
color: '#333',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[30px] flex justify-center items-center">
|
||||
<IconButton
|
||||
name="edit"
|
||||
onClick={() => {
|
||||
editKey(key)
|
||||
}}
|
||||
sx={{
|
||||
color: '#333',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
<IconButton
|
||||
name="delete"
|
||||
onClick={() => {
|
||||
deleteKey(key.id)
|
||||
}}
|
||||
sx={{
|
||||
color: '#333',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': { background: 'transparent', color: 'red', transition: 'none' }
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))
|
||||
) : (
|
||||
<>
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</InsidePage>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default McpKeyContainer
|
||||
@@ -0,0 +1,37 @@
|
||||
import InsidePage from "@common/components/aoplatform/InsidePage"
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { IntegrationAIContainer } from "./IntegrationAIContainer"
|
||||
import { Tool } from "@modelcontextprotocol/sdk/types.js"
|
||||
import { useEffect, useState } from "react"
|
||||
import McpToolsContainer from "./McpToolsContainer"
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
|
||||
|
||||
const McpServiceContainer = () => {
|
||||
const [tools, setTools] = useState<Tool[]>([]);
|
||||
const [, forceUpdate] = useState<unknown>(null)
|
||||
const { state } = useGlobalContext()
|
||||
const handleToolsChange = (value: Tool[]) => {
|
||||
setTools(value)
|
||||
}
|
||||
useEffect(() => {
|
||||
forceUpdate({})
|
||||
}, [state.language])
|
||||
return (
|
||||
<>
|
||||
<InsidePage
|
||||
pageTitle={$t('MCP 服务')}
|
||||
description={$t('MCP Service 充当 AI 模型与 API 之间的桥梁,允许智能助手(如 Claude)动态发现和调用 Gateway 上的 API,无需繁琐的手动配置或自定义集成。')}
|
||||
showBorder={false}
|
||||
scrollPage={false}
|
||||
>
|
||||
|
||||
<div className="flex mt-[10px] pr-[40px]">
|
||||
<McpToolsContainer tools={tools} />
|
||||
<IntegrationAIContainer type={'global'} handleToolsChange={handleToolsChange}></IntegrationAIContainer>
|
||||
</div>
|
||||
</InsidePage>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default McpServiceContainer
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { Tool } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { Card } from 'antd'
|
||||
|
||||
const McpToolsContainer = ({ tools = [], customClassName }: { tools: Tool[]; customClassName?: string }) => {
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
style={{ borderRadius: '10px' }}
|
||||
className={`w-full flex-1 mr-[10px] ${customClassName}`}
|
||||
classNames={{
|
||||
body: 'p-[10px]'
|
||||
}}
|
||||
>
|
||||
<div className="mb-[10px]">
|
||||
<Icon icon="gravity-ui:plug-connection" className="align-text-bottom mr-[5px]" width="16" height="16" />
|
||||
<span className="text-[14px] font-bold align-middle">Tools</span>
|
||||
</div>
|
||||
{tools.map((tool, index) => (
|
||||
<Card style={{ borderRadius: '10px' }} key={index} className={`w-full ${index > 0 ? 'mt-[10px]' : ''}`}>
|
||||
<p className="text-[14px] font-bold">{tool.name}</p>
|
||||
<div className="leading-[28px]">{tool.description}</div>
|
||||
</Card>
|
||||
))}
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default McpToolsContainer
|
||||
@@ -0,0 +1,33 @@
|
||||
// import { InspectorConfig } from "./configurationTypes";
|
||||
|
||||
// OAuth-related session storage keys
|
||||
export const SESSION_KEYS = {
|
||||
CODE_VERIFIER: "mcp_code_verifier",
|
||||
SERVER_URL: "mcp_server_url",
|
||||
TOKENS: "mcp_tokens",
|
||||
CLIENT_INFORMATION: "mcp_client_information",
|
||||
} as const;
|
||||
|
||||
export type ConnectionStatus =
|
||||
| "disconnected"
|
||||
| "connected"
|
||||
| "error"
|
||||
| "error-connecting-to-proxy";
|
||||
|
||||
export const DEFAULT_MCP_PROXY_LISTEN_PORT = "6277";
|
||||
|
||||
/**
|
||||
* Default configuration for the MCP Inspector, Currently persisted in local_storage in the Browser.
|
||||
* Future plans: Provide json config file + Browser local_storage to override default values
|
||||
**/
|
||||
export const DEFAULT_INSPECTOR_CONFIG: any = {
|
||||
MCP_SERVER_REQUEST_TIMEOUT: {
|
||||
description: "Timeout for requests to the MCP server (ms)",
|
||||
value: 10000,
|
||||
},
|
||||
MCP_PROXY_FULL_ADDRESS: {
|
||||
description:
|
||||
"Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577",
|
||||
value: "",
|
||||
},
|
||||
} as const;
|
||||
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
NotificationSchema as BaseNotificationSchema,
|
||||
ClientNotificationSchema,
|
||||
ServerNotificationSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export const StdErrNotificationSchema = BaseNotificationSchema.extend({
|
||||
method: z.literal("notifications/stderr"),
|
||||
params: z.object({
|
||||
content: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const NotificationSchema = ClientNotificationSchema.or(
|
||||
StdErrNotificationSchema,
|
||||
)
|
||||
.or(ServerNotificationSchema)
|
||||
.or(BaseNotificationSchema);
|
||||
|
||||
export type StdErrNotification = z.infer<typeof StdErrNotificationSchema>;
|
||||
export type Notification = z.infer<typeof NotificationSchema>;
|
||||
@@ -0,0 +1,391 @@
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import {
|
||||
SSEClientTransport,
|
||||
SseError,
|
||||
} from "@modelcontextprotocol/sdk/client/sse.js";
|
||||
import { App } from 'antd'
|
||||
import {
|
||||
ClientNotification,
|
||||
ClientRequest,
|
||||
CreateMessageRequestSchema,
|
||||
ListRootsRequestSchema,
|
||||
ProgressNotificationSchema,
|
||||
ResourceUpdatedNotificationSchema,
|
||||
LoggingMessageNotificationSchema,
|
||||
Request,
|
||||
Result,
|
||||
ServerCapabilities,
|
||||
PromptReference,
|
||||
ResourceReference,
|
||||
McpError,
|
||||
CompleteResultSchema,
|
||||
ErrorCode,
|
||||
CancelledNotificationSchema,
|
||||
ResourceListChangedNotificationSchema,
|
||||
ToolListChangedNotificationSchema,
|
||||
PromptListChangedNotificationSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { useState } from "react";
|
||||
import { z } from "zod";
|
||||
import { ConnectionStatus, SESSION_KEYS } from "./constants";
|
||||
import { Notification, StdErrNotificationSchema } from "./notificationTypes";
|
||||
// import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
|
||||
// import { authProvider } from "../auth";
|
||||
// import packageJson from "../../../package.json";
|
||||
|
||||
|
||||
interface UseConnectionOptions {
|
||||
transportType: "stdio" | "sse";
|
||||
command?: string;
|
||||
args?: string;
|
||||
sseUrl: string;
|
||||
env?: Record<string, string>;
|
||||
proxyServerUrl: string;
|
||||
bearerToken?: string;
|
||||
requestTimeout?: number;
|
||||
onNotification?: (notification: Notification) => void;
|
||||
onStdErrNotification?: (notification: Notification) => void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onPendingRequest?: (request: any, resolve: any, reject: any) => void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getRoots?: () => any[];
|
||||
}
|
||||
|
||||
interface RequestOptions {
|
||||
signal?: AbortSignal;
|
||||
timeout?: number;
|
||||
suppressToast?: boolean;
|
||||
}
|
||||
|
||||
export function useConnection({
|
||||
transportType,
|
||||
command,
|
||||
args,
|
||||
sseUrl,
|
||||
env,
|
||||
proxyServerUrl,
|
||||
bearerToken,
|
||||
requestTimeout,
|
||||
onNotification,
|
||||
onStdErrNotification,
|
||||
onPendingRequest,
|
||||
getRoots,
|
||||
}: UseConnectionOptions) {
|
||||
const [connectionStatus, setConnectionStatus] =
|
||||
useState<ConnectionStatus>("disconnected");
|
||||
const { message } = App.useApp()
|
||||
const [serverCapabilities, setServerCapabilities] =
|
||||
useState<ServerCapabilities | null>(null);
|
||||
const [mcpClient, setMcpClient] = useState<Client | null>(null);
|
||||
const [requestHistory, setRequestHistory] = useState<
|
||||
{ request: string; response?: string }[]
|
||||
>([]);
|
||||
const [completionsSupported, setCompletionsSupported] = useState(true);
|
||||
|
||||
const pushHistory = (request: object, response?: object) => {
|
||||
setRequestHistory((prev) => [
|
||||
...prev,
|
||||
{
|
||||
request: JSON.stringify(request),
|
||||
response: response !== undefined ? JSON.stringify(response) : undefined,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const makeRequest = async <T extends z.ZodType>(
|
||||
request: ClientRequest,
|
||||
schema: T,
|
||||
options?: RequestOptions,
|
||||
): Promise<z.output<T>> => {
|
||||
if (!mcpClient) {
|
||||
throw new Error("MCP client not connected");
|
||||
}
|
||||
|
||||
try {
|
||||
const abortController = new AbortController();
|
||||
const timeoutId = setTimeout(() => {
|
||||
abortController.abort("Request timed out");
|
||||
}, options?.timeout ?? requestTimeout);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await mcpClient.request(request, schema, {
|
||||
signal: options?.signal ?? abortController.signal,
|
||||
});
|
||||
pushHistory(request, response);
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
pushHistory(request, { error: errorMessage });
|
||||
throw error;
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (e: unknown) {
|
||||
if (!options?.suppressToast) {
|
||||
const errorString = (e as Error).message ?? String(e);
|
||||
message.error(errorString)
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCompletion = async (
|
||||
ref: ResourceReference | PromptReference,
|
||||
argName: string,
|
||||
value: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<string[]> => {
|
||||
if (!mcpClient || !completionsSupported) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const request: ClientRequest = {
|
||||
method: "completion/complete",
|
||||
params: {
|
||||
argument: {
|
||||
name: argName,
|
||||
value,
|
||||
},
|
||||
ref,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await makeRequest(request, CompleteResultSchema, {
|
||||
signal,
|
||||
suppressToast: true,
|
||||
});
|
||||
return response?.completion.values || [];
|
||||
} catch (e: unknown) {
|
||||
// Disable completions silently if the server doesn't support them.
|
||||
// See https://github.com/modelcontextprotocol/specification/discussions/122
|
||||
if (e instanceof McpError && e.code === ErrorCode.MethodNotFound) {
|
||||
setCompletionsSupported(false);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Unexpected errors - show toast and rethrow
|
||||
message.error(e instanceof Error ? e.message : String(e))
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
const sendNotification = async (notification: ClientNotification) => {
|
||||
if (!mcpClient) {
|
||||
const error = new Error("MCP client not connected");
|
||||
message.error(error.message)
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
await mcpClient.notification(notification);
|
||||
// Log successful notifications
|
||||
pushHistory(notification);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof McpError) {
|
||||
// Log MCP protocol errors
|
||||
pushHistory(notification, { error: e.message });
|
||||
}
|
||||
message.error(e instanceof Error ? e.message : String(e))
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
// TODO_先屏蔽,暂时不需要
|
||||
// const checkProxyHealth = async () => {
|
||||
// try {
|
||||
// const proxyHealthUrl = new URL(`${proxyServerUrl}/health`);
|
||||
// const proxyHealthResponse = await fetch(proxyHealthUrl);
|
||||
// const proxyHealth = await proxyHealthResponse.json();
|
||||
// if (proxyHealth?.status !== "ok") {
|
||||
// throw new Error("MCP Proxy Server is not healthy");
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.error("Couldn't connect to MCP Proxy Server", e);
|
||||
// throw e;
|
||||
// }
|
||||
// };
|
||||
// TODO_先屏蔽,暂时不需要
|
||||
// const handleAuthError = async (error: unknown) => {
|
||||
// if (error instanceof SseError && error.code === 401) {
|
||||
// sessionStorage.setItem(SESSION_KEYS.SERVER_URL, sseUrl);
|
||||
|
||||
// const result = await auth(authProvider, { serverUrl: sseUrl });
|
||||
// return result === "AUTHORIZED";
|
||||
// }
|
||||
|
||||
// return false;
|
||||
// };
|
||||
|
||||
const connect = async (_e?: unknown, retryCount: number = 0) => {
|
||||
const client = new Client<Request, Notification, Result>(
|
||||
{
|
||||
name: "mcp-inspector",
|
||||
version: '0.0.1',
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
sampling: {},
|
||||
roots: {
|
||||
listChanged: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
// TODO_暂时不需要
|
||||
// try {
|
||||
// await checkProxyHealth();
|
||||
// } catch {
|
||||
// setConnectionStatus("error-connecting-to-proxy");
|
||||
// return;
|
||||
// }
|
||||
// 使用与http.ts一致的方式处理URL
|
||||
// 注意:proxyServerUrl应该是完整URL,或者我们需要为其添加基础URL
|
||||
// 处理两种情况:完整URL或相对路径
|
||||
let fullUrl;
|
||||
if (proxyServerUrl.startsWith('http://') || proxyServerUrl.startsWith('https://')) {
|
||||
// 如果是完整URL,直接使用
|
||||
fullUrl = `${proxyServerUrl}/sse`;
|
||||
} else {
|
||||
// 如果是相对路径,添加基础URL和API前缀
|
||||
const baseUrl = window.location.origin;
|
||||
const apiPrefix = '/api/v1/';
|
||||
fullUrl = `${baseUrl}${apiPrefix}${proxyServerUrl}`;
|
||||
}
|
||||
// let newSseUrl = ''
|
||||
// if (sseUrl.startsWith('http://') || sseUrl.startsWith('https://')) {
|
||||
// // 如果是完整URL,直接使用
|
||||
// newSseUrl = sseUrl
|
||||
// } else {
|
||||
// // 如果是相对路径,添加基础URL和API前缀
|
||||
// const baseUrl = window.location.origin;
|
||||
// const apiPrefix = '/api/v1/';
|
||||
// newSseUrl = `${baseUrl}${apiPrefix}${sseUrl}`;
|
||||
// }
|
||||
const mcpProxyServerUrl = new URL(fullUrl);
|
||||
// mcpProxyServerUrl.searchParams.append("transportType", transportType);
|
||||
// if (transportType === "stdio") {
|
||||
// mcpProxyServerUrl.searchParams.append("command", command || '');
|
||||
// mcpProxyServerUrl.searchParams.append("args", args || '');
|
||||
// mcpProxyServerUrl.searchParams.append("env", JSON.stringify(env || {}));
|
||||
// } else {
|
||||
// mcpProxyServerUrl.searchParams.append("url", newSseUrl);
|
||||
// }
|
||||
// console.log('sseUrl===', newSseUrl)
|
||||
try {
|
||||
// Inject auth manually instead of using SSEClientTransport, because we're
|
||||
// proxying through the inspector server first.
|
||||
const headers: HeadersInit = {};
|
||||
|
||||
// TODO_暂时不需要。Use manually provided bearer token if available, otherwise use OAuth tokens
|
||||
// const token = bearerToken || (await authProvider.tokens())?.access_token;
|
||||
// if (token) {
|
||||
// headers["Authorization"] = `Bearer ${token}`;
|
||||
// }
|
||||
// 创建SSE客户端传输层
|
||||
const clientTransport = new SSEClientTransport(mcpProxyServerUrl, {
|
||||
eventSourceInit: {
|
||||
fetch: (url, init) => fetch(url, { ...init, headers }),
|
||||
},
|
||||
requestInit: {
|
||||
headers,
|
||||
},
|
||||
});
|
||||
// TODO_暂时不需要
|
||||
// if (onNotification) {
|
||||
// [
|
||||
// CancelledNotificationSchema,
|
||||
// ProgressNotificationSchema,
|
||||
// LoggingMessageNotificationSchema,
|
||||
// ResourceUpdatedNotificationSchema,
|
||||
// ResourceListChangedNotificationSchema,
|
||||
// ToolListChangedNotificationSchema,
|
||||
// PromptListChangedNotificationSchema,
|
||||
// ].forEach((notificationSchema) => {
|
||||
// client.setNotificationHandler(notificationSchema, onNotification);
|
||||
// });
|
||||
|
||||
// client.fallbackNotificationHandler = (
|
||||
// notification: Notification,
|
||||
// ): Promise<void> => {
|
||||
// onNotification(notification);
|
||||
// return Promise.resolve();
|
||||
// };
|
||||
// }
|
||||
|
||||
// if (onStdErrNotification) {
|
||||
// client.setNotificationHandler(
|
||||
// StdErrNotificationSchema,
|
||||
// onStdErrNotification,
|
||||
// );
|
||||
// }
|
||||
|
||||
try {
|
||||
await client.connect(clientTransport);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to connect to MCP Server via the MCP Inspector Proxy: ${mcpProxyServerUrl}:`,
|
||||
error,
|
||||
);
|
||||
// TODO_先屏蔽,后续如果需要再处理
|
||||
// const shouldRetry = await handleAuthError(error);
|
||||
// if (shouldRetry) {
|
||||
// return connect(undefined, retryCount + 1);
|
||||
// }
|
||||
|
||||
if (error instanceof SseError && error.code === 401) {
|
||||
// Don't set error state if we're about to redirect for auth
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const capabilities = client.getServerCapabilities();
|
||||
setServerCapabilities(capabilities ?? null);
|
||||
setCompletionsSupported(true); // Reset completions support on new connection
|
||||
// TODO_暂时不需要
|
||||
// if (onPendingRequest) {
|
||||
// client.setRequestHandler(CreateMessageRequestSchema, (request) => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// onPendingRequest(request, resolve, reject);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (getRoots) {
|
||||
// client.setRequestHandler(ListRootsRequestSchema, async () => {
|
||||
// return { roots: getRoots() };
|
||||
// });
|
||||
// }
|
||||
|
||||
setMcpClient(client);
|
||||
setConnectionStatus("connected");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setConnectionStatus("error");
|
||||
}
|
||||
};
|
||||
|
||||
const disconnect = async () => {
|
||||
await mcpClient?.close();
|
||||
setMcpClient(null);
|
||||
setConnectionStatus("disconnected");
|
||||
setCompletionsSupported(false);
|
||||
setServerCapabilities(null);
|
||||
};
|
||||
|
||||
return {
|
||||
connectionStatus,
|
||||
serverCapabilities,
|
||||
mcpClient,
|
||||
requestHistory,
|
||||
makeRequest,
|
||||
sendNotification,
|
||||
handleCompletion,
|
||||
completionsSupported,
|
||||
connect,
|
||||
disconnect,
|
||||
};
|
||||
}
|
||||
@@ -480,6 +480,8 @@ const MemberList = () => {
|
||||
render: (_, entity) => (
|
||||
<WithPermission access="system.organization.member.edit">
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-full"
|
||||
mode="multiple"
|
||||
value={entity.roles?.map((x: EntityItem) => x.id)}
|
||||
|
||||
@@ -60,7 +60,13 @@ export type DashboardSettingEditProps = {
|
||||
name="driver"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.select)} options={[...DASHBOARD_SETTING_DRIVER_OPTION_LIST]}/>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={[...DASHBOARD_SETTING_DRIVER_OPTION_LIST]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<PartitionDashboardConfigFieldType>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -301,7 +301,13 @@ const FilterForm = forwardRef<FilterFormHandle, FilterFormProps>(
|
||||
return (
|
||||
<Form form={form} layout="vertical" onValuesChange={handleValuesChange}>
|
||||
<Form.Item name="name" label={$t('属性名称')} rules={[{ required: true }]}>
|
||||
<Select disabled={disabled} onChange={handleTypeChange} options={filterOptionsList} />
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
disabled={disabled}
|
||||
onChange={handleTypeChange}
|
||||
options={filterOptionsList}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="values"
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useEffect } from "react";
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import { useEffect } from 'react'
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
export default function ServicePolicyLayout(){
|
||||
const location = useLocation()
|
||||
const pathName = location.pathname
|
||||
const navigator = useNavigate()
|
||||
useEffect(()=>{
|
||||
const tmpPath = pathName.split('/')
|
||||
if(tmpPath[tmpPath.length -1 ] === 'servicepolicy'){
|
||||
navigator('datamasking/list')
|
||||
}
|
||||
},[pathName])
|
||||
return (<Outlet></Outlet>)
|
||||
}
|
||||
export default function ServicePolicyLayout() {
|
||||
const location = useLocation()
|
||||
const pathName = location.pathname
|
||||
const navigator = useNavigate()
|
||||
useEffect(() => {
|
||||
const tmpPath = pathName.split('/')
|
||||
if (tmpPath[tmpPath.length - 1] === 'servicepolicy') {
|
||||
navigator('datamasking/list')
|
||||
}
|
||||
}, [pathName])
|
||||
return <Outlet></Outlet>
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import FilterTable from "../FilterTable"
|
||||
import { DataMaskingConfigHandle ,DataMaskingConfigFieldType, PolicyMatchType} from "@common/const/policy/type"
|
||||
import {PolicyOptions} from '@common/const/policy/consts'
|
||||
import {v4 as uuidv4} from 'uuid'
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"
|
||||
|
||||
const DataMaskingConfig = forwardRef<DataMaskingConfigHandle>((_,ref) => {
|
||||
const { message,modal } = App.useApp()
|
||||
@@ -24,6 +25,7 @@ const DataMaskingConfig = forwardRef<DataMaskingConfigHandle>((_,ref) => {
|
||||
const { state } = useGlobalContext()
|
||||
const [ loading, setLoading ] = useState<boolean>(false)
|
||||
const navigator = useNavigate()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
save:onFinish
|
||||
@@ -96,7 +98,7 @@ const DataMaskingConfig = forwardRef<DataMaskingConfigHandle>((_,ref) => {
|
||||
showBorder={false}
|
||||
scrollPage={false}
|
||||
className="overflow-y-auto"
|
||||
backUrl={serviceId ? '../list' : undefined}
|
||||
backUrl={serviceId ? `/service/${teamId}/aiInside/${serviceId}/servicepolicy` : undefined}
|
||||
>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} wrapperClassName=' pb-PAGE_INSIDE_B pr-PAGE_INSIDE_X'>
|
||||
<WithPermission access={onEdit ? [`${ serviceId === undefined ? 'system.devops':'team.service'}.policy.edit`] :''}>
|
||||
@@ -124,8 +126,13 @@ const DataMaskingConfig = forwardRef<DataMaskingConfigHandle>((_,ref) => {
|
||||
name="type"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} options={policyOptions} >
|
||||
</Select>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
options={policyOptions}
|
||||
></Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<DataMaskingConfigFieldType>
|
||||
|
||||
@@ -146,6 +146,8 @@ const DataMaskRuleForm: React.FC<DataMaskRuleFormProps> = ({
|
||||
<Form form={form} layout="vertical" className="p-4">
|
||||
<Form.Item name={['match', 'type']} label={$t('匹配类型')} rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
onChange={handleMatchTypeChange}
|
||||
options={matchRuleOptions}
|
||||
@@ -156,6 +158,8 @@ const DataMaskRuleForm: React.FC<DataMaskRuleFormProps> = ({
|
||||
<Form.Item name={['match', 'value']} label={$t('匹配值')} rules={[{ required: true }]}>
|
||||
{matchType === 'inner' ? (
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
onChange={handleMatchValueChange}
|
||||
options={dataFormatOptions}
|
||||
@@ -168,6 +172,8 @@ const DataMaskRuleForm: React.FC<DataMaskRuleFormProps> = ({
|
||||
|
||||
<Form.Item name={['mask', 'type']} label={$t('脱敏类型')} rules={[{ required: true }]}>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
onChange={handleMaskTypeChange}
|
||||
options={
|
||||
@@ -197,6 +203,8 @@ const DataMaskRuleForm: React.FC<DataMaskRuleFormProps> = ({
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
onChange={handleReplaceTypeChange}
|
||||
options={dataMaskReplaceStrOptions}
|
||||
|
||||
@@ -1,251 +1,313 @@
|
||||
import { useEffect, useMemo, useState} from "react";
|
||||
import {App, Button, Checkbox, Collapse, Form, GetProp, Input} from "antd";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx";
|
||||
import { ArrowLeftOutlined } from "@ant-design/icons";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { App, Button, Checkbox, Collapse, Form, GetProp, Input } from 'antd'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
|
||||
import TopBreadcrumb from '@common/components/aoplatform/Breadcrumb.tsx'
|
||||
|
||||
type PermissionItem = {
|
||||
name:string
|
||||
value:string
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
|
||||
type PermissionClassify = PermissionItem & {children : ( PermissionItem & {dependents:string[]})[]}
|
||||
type PermissionClassify = PermissionItem & { children: (PermissionItem & { dependents: string[] })[] }
|
||||
|
||||
type RolePermissionItem = PermissionItem & {
|
||||
children:PermissionClassify[]}
|
||||
children: PermissionClassify[]
|
||||
}
|
||||
|
||||
|
||||
type DependenciesMapType = Map<string, {dependents:string[], control:string[]}>
|
||||
type DependenciesMapType = Map<string, { dependents: string[]; control: string[] }>
|
||||
|
||||
type PermissionCollapseProps = {
|
||||
id?: string;
|
||||
value?: string[];
|
||||
onChange?: (value:string[]) => void;
|
||||
permissionTemplate:RolePermissionItem[]
|
||||
dependenciesMap?: DependenciesMapType
|
||||
id?: string
|
||||
value?: string[]
|
||||
onChange?: (value: string[]) => void
|
||||
permissionTemplate: RolePermissionItem[]
|
||||
dependenciesMap?: DependenciesMapType
|
||||
}
|
||||
|
||||
type PermissionInfo = {
|
||||
permit: string[]
|
||||
description: string
|
||||
update_time: string
|
||||
create_time: string
|
||||
name: string
|
||||
permit: string[]
|
||||
description: string
|
||||
update_time: string
|
||||
create_time: string
|
||||
name: string
|
||||
}
|
||||
|
||||
const PermissionContent = ({permits,onChange,value=[],id,dependenciesMap}:{permits:PermissionClassify[],dependenciesMap:DependenciesMapType,value:string[],id:string, onChange?: (value:string[]) => void;})=>{
|
||||
|
||||
|
||||
const onSingleCheckboxChange: GetProp<typeof Checkbox, 'onChange'> = (e) => {
|
||||
if(e.target.checked){
|
||||
onChange?.(Array.from(new Set([...value, e.target.id, ...(dependenciesMap?.get(e.target.id!)?.dependents || [])] as string[])))
|
||||
}else{
|
||||
const cancelValue = [...dependenciesMap?.get(e.target.id!)?.control || [], e.target.id]
|
||||
onChange?.(value.filter(x=>!cancelValue.includes(x)))
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div id={id} className="flex flex-col gap-btnbase p-btnbase">
|
||||
{
|
||||
permits.map((item:PermissionClassify)=>(
|
||||
<>
|
||||
<div className="flex flex-col gap-btnbase" key={`group-${item.name}`}>
|
||||
{item.name !== '' && <p className="">{(item.name)}</p>}
|
||||
<div className=" pl-[20px]">
|
||||
{item.children.map(x=><Checkbox id={x.value} key={x.value} checked={value && value.length > 0 && value.indexOf(x.value)>-1} onChange={onSingleCheckboxChange}>{(x.name)}</Checkbox>)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
}</div>
|
||||
)
|
||||
}
|
||||
// 自定义表单控件
|
||||
const PermissionCollapse:React.FC<PermissionCollapseProps> = (props)=>{
|
||||
const { id, value = [], onChange,permissionTemplate ,dependenciesMap} = props;
|
||||
const [openCollapses, setOpenCollapses] = useState<string[]>([])
|
||||
const {state} = useGlobalContext()
|
||||
|
||||
const items = useMemo(()=>{
|
||||
const generatePermissionItem = (permissionItem:RolePermissionItem[])=> permissionItem.map((item:RolePermissionItem)=>({
|
||||
key:item.name,
|
||||
label:(item.name),
|
||||
children:<PermissionContent value={value} permits={item.children} onChange={(e)=>onChange?.(e)} id={id!} dependenciesMap={dependenciesMap!}/>
|
||||
}))
|
||||
return permissionTemplate && permissionTemplate.length > 0 ? generatePermissionItem(permissionTemplate) : []
|
||||
},[permissionTemplate,value,state.language])
|
||||
|
||||
useEffect(()=>{
|
||||
permissionTemplate && setOpenCollapses(permissionTemplate?.map(x=>x.name))
|
||||
},[permissionTemplate])
|
||||
|
||||
const onCollapseChange = (keys: string | string[]) => {
|
||||
setOpenCollapses(keys as string[])
|
||||
};
|
||||
|
||||
return <Collapse items={items} activeKey={openCollapses} onChange={onCollapseChange} />
|
||||
const PermissionContent = ({
|
||||
permits,
|
||||
onChange,
|
||||
value = [],
|
||||
id,
|
||||
dependenciesMap
|
||||
}: {
|
||||
permits: PermissionClassify[]
|
||||
dependenciesMap: DependenciesMapType
|
||||
value: string[]
|
||||
id: string
|
||||
onChange?: (value: string[]) => void
|
||||
}) => {
|
||||
const onSingleCheckboxChange: GetProp<typeof Checkbox, 'onChange'> = (e) => {
|
||||
if (e.target.checked) {
|
||||
onChange?.(
|
||||
Array.from(
|
||||
new Set([...value, e.target.id, ...(dependenciesMap?.get(e.target.id!)?.dependents || [])] as string[])
|
||||
)
|
||||
)
|
||||
} else {
|
||||
const cancelValue = [...(dependenciesMap?.get(e.target.id!)?.control || []), e.target.id]
|
||||
onChange?.(value.filter((x) => !cancelValue.includes(x)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const RoleConfig = ()=>{
|
||||
const { message } = App.useApp()
|
||||
const [form] = Form.useForm();
|
||||
const {fetchData} = useFetch()
|
||||
const navigateTo = useNavigate()
|
||||
const { roleType, roleId} = useParams<RouterParams>()
|
||||
const [permissionTemplate, setPermissionTemplate] = useState<RolePermissionItem[]>()
|
||||
const [dependenciesMap, setDependenciesMap] = useState<DependenciesMapType>()
|
||||
const APP_MODE = import.meta.env.VITE_APP_MODE;
|
||||
const [permissionInfo, setPermissionInfo] = useState<PermissionInfo>()
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
const generateDependenciesMap = (data:RolePermissionItem[])=>{
|
||||
const map = new Map<string, {dependents:string[], control:string[]}>()
|
||||
data.forEach((item:RolePermissionItem)=>{
|
||||
item.children.forEach((child:PermissionClassify)=>{
|
||||
child.children.forEach((permission:PermissionItem & {dependents:string[]})=>{
|
||||
|
||||
if (permission.dependents && permission.dependents.length > 0) {
|
||||
// 获取当前权限的依赖
|
||||
const currentDependents = map.get(permission.value);
|
||||
if (currentDependents) {
|
||||
currentDependents.dependents.push(...permission.dependents);
|
||||
} else {
|
||||
map.set(permission.value, { dependents: [...permission.dependents], control: [] });
|
||||
}
|
||||
|
||||
// 更新依赖项的控制项
|
||||
permission.dependents.forEach((dependent: string) => {
|
||||
const dependentEntry = map.get(dependent);
|
||||
if (dependentEntry) {
|
||||
dependentEntry.control.push(permission.value);
|
||||
} else {
|
||||
map.set(dependent, { dependents: [], control: [permission.value] });
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
setDependenciesMap(map)
|
||||
}
|
||||
|
||||
const generateNewPermit:(data:RolePermissionItem[])=>RolePermissionItem[] = (data:RolePermissionItem[]) =>{
|
||||
return data.map((item:RolePermissionItem)=>({
|
||||
...item,children:item.children.map((child:PermissionClassify)=>({
|
||||
...child,
|
||||
children:child.children.map((permission:PermissionItem & {dependents:string[]})=>({
|
||||
...permission, value:`${roleType}.${item.value}.${child.value}.${permission.value}`
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
}
|
||||
|
||||
const getPermissionTemplate = ()=>{
|
||||
return fetchData<BasicResponse<{permits:RolePermissionItem[]}>>(`${roleType}/role/template`,{method:'GET'}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
const newPermits = generateNewPermit(data.permits)
|
||||
generateDependenciesMap(newPermits)
|
||||
setPermissionTemplate(newPermits)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.dataError))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getPermissionInfo = ()=>{
|
||||
fetchData<BasicResponse<{role:PermissionInfo}>>(`${roleType}/role`,{method:'GET',eoParams:{role:roleId}}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setPermissionInfo(data.role)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errInfo)=>console.error(errInfo))
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
form.setFieldsValue({name:$t(permissionInfo?.name || ''),permits:permissionInfo?.permit})
|
||||
},[permissionInfo, state.language])
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({name:'',permits:[]})
|
||||
if(roleId){
|
||||
getPermissionInfo()
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(()=>{
|
||||
getPermissionTemplate()
|
||||
|
||||
},[state.language])
|
||||
|
||||
const onFinish =async() => {
|
||||
const body = await form.validateFields()
|
||||
|
||||
return fetchData<BasicResponse<null>>(`${roleType}/role`,{method:roleId === undefined? 'POST' : 'PUT',eoBody:({...body}),...(roleId !== undefined?{eoParams:{role:roleId}}:{})}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return Promise.resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errInfo)=>Promise.reject(errInfo))
|
||||
};
|
||||
|
||||
return (<div className="h-full flex flex-col overflow-hidden ">
|
||||
<div className="text-[18px] leading-[25px] pb-[12px]">
|
||||
<Button className="flex items-center" type="text" onClick={()=>navigateTo(-1)}><ArrowLeftOutlined className="max-h-[14px]" /><span>{$t('返回')}</span></Button>
|
||||
</div>
|
||||
<WithPermission access={roleId !== undefined ? `system.organization.role.${roleType}.edit`: `system.organization.role.${roleType}.add`}>
|
||||
<Form
|
||||
id="permission"
|
||||
layout='vertical'
|
||||
labelAlign='left'
|
||||
scrollToFirstError
|
||||
form={form}
|
||||
className="mx-auto w-full flex-1 no-bg-form overflow-hidden "
|
||||
name="rolePermissionConfig"
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
return (
|
||||
<div id={id} className="flex flex-col gap-btnbase p-btnbase">
|
||||
{permits.map((item: PermissionClassify) => (
|
||||
<>
|
||||
<div className="flex flex-col gap-btnbase" key={`group-${item.name}`}>
|
||||
{item.name !== '' && <p className="">{item.name}</p>}
|
||||
<div className=" pl-[20px]">
|
||||
{item.children.map((x) => (
|
||||
<Checkbox
|
||||
id={x.value}
|
||||
key={x.value}
|
||||
checked={value && value.length > 0 && value.indexOf(x.value) > -1}
|
||||
onChange={onSingleCheckboxChange}
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
<Form.Item
|
||||
className=" m-btnbase mr-PAGE_INSIDE_X"
|
||||
name="name"
|
||||
rules={[{ required: true,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="permits"
|
||||
className="m-btnbase mr-0 flex-1 overflow-auto pr-PAGE_INSIDE_X"
|
||||
>
|
||||
<PermissionCollapse permissionTemplate={permissionTemplate!} dependenciesMap={dependenciesMap} />
|
||||
</Form.Item>
|
||||
|
||||
{APP_MODE === 'pro' && <div className="p-btnbase">
|
||||
<WithPermission access={roleId === undefined ?`system.organization.role.${roleType}.edit`:`system.organization.role.${roleType}.add`}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{$t('保存')}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
<Button className="ml-btnrbase" type="default" onClick={() => navigateTo(-1)}>
|
||||
{$t('取消')}
|
||||
</Button>
|
||||
</div>}
|
||||
</div>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</div>)
|
||||
{x.name}
|
||||
</Checkbox>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default RoleConfig
|
||||
// 自定义表单控件
|
||||
const PermissionCollapse: React.FC<PermissionCollapseProps> = (props) => {
|
||||
const { id, value = [], onChange, permissionTemplate, dependenciesMap } = props
|
||||
const [openCollapses, setOpenCollapses] = useState<string[]>([])
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
const items = useMemo(() => {
|
||||
const generatePermissionItem = (permissionItem: RolePermissionItem[]) =>
|
||||
permissionItem.map((item: RolePermissionItem) => ({
|
||||
key: item.name,
|
||||
label: item.name,
|
||||
children: (
|
||||
<PermissionContent
|
||||
value={value}
|
||||
permits={item.children}
|
||||
onChange={(e) => onChange?.(e)}
|
||||
id={id!}
|
||||
dependenciesMap={dependenciesMap!}
|
||||
/>
|
||||
)
|
||||
}))
|
||||
return permissionTemplate && permissionTemplate.length > 0 ? generatePermissionItem(permissionTemplate) : []
|
||||
}, [permissionTemplate, value, state.language])
|
||||
|
||||
useEffect(() => {
|
||||
permissionTemplate && setOpenCollapses(permissionTemplate?.map((x) => x.name))
|
||||
}, [permissionTemplate])
|
||||
|
||||
const onCollapseChange = (keys: string | string[]) => {
|
||||
setOpenCollapses(keys as string[])
|
||||
}
|
||||
|
||||
return <Collapse items={items} activeKey={openCollapses} onChange={onCollapseChange} />
|
||||
}
|
||||
|
||||
const RoleConfig = () => {
|
||||
const { message } = App.useApp()
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
const navigateTo = useNavigate()
|
||||
const { roleType, roleId } = useParams<RouterParams>()
|
||||
const [permissionTemplate, setPermissionTemplate] = useState<RolePermissionItem[]>()
|
||||
const [dependenciesMap, setDependenciesMap] = useState<DependenciesMapType>()
|
||||
const APP_MODE = import.meta.env.VITE_APP_MODE
|
||||
const [permissionInfo, setPermissionInfo] = useState<PermissionInfo>()
|
||||
const { state } = useGlobalContext()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
|
||||
const generateDependenciesMap = (data: RolePermissionItem[]) => {
|
||||
const map = new Map<string, { dependents: string[]; control: string[] }>()
|
||||
data.forEach((item: RolePermissionItem) => {
|
||||
item.children.forEach((child: PermissionClassify) => {
|
||||
child.children.forEach((permission: PermissionItem & { dependents: string[] }) => {
|
||||
if (permission.dependents && permission.dependents.length > 0) {
|
||||
// 获取当前权限的依赖
|
||||
const currentDependents = map.get(permission.value)
|
||||
if (currentDependents) {
|
||||
currentDependents.dependents.push(...permission.dependents)
|
||||
} else {
|
||||
map.set(permission.value, { dependents: [...permission.dependents], control: [] })
|
||||
}
|
||||
|
||||
// 更新依赖项的控制项
|
||||
permission.dependents.forEach((dependent: string) => {
|
||||
const dependentEntry = map.get(dependent)
|
||||
if (dependentEntry) {
|
||||
dependentEntry.control.push(permission.value)
|
||||
} else {
|
||||
map.set(dependent, { dependents: [], control: [permission.value] })
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
setDependenciesMap(map)
|
||||
}
|
||||
|
||||
const generateNewPermit: (data: RolePermissionItem[]) => RolePermissionItem[] = (data: RolePermissionItem[]) => {
|
||||
return data.map((item: RolePermissionItem) => ({
|
||||
...item,
|
||||
children: item.children.map((child: PermissionClassify) => ({
|
||||
...child,
|
||||
children: child.children.map((permission: PermissionItem & { dependents: string[] }) => ({
|
||||
...permission,
|
||||
value: `${roleType}.${item.value}.${child.value}.${permission.value}`
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
}
|
||||
|
||||
const getPermissionTemplate = () => {
|
||||
return fetchData<BasicResponse<{ permits: RolePermissionItem[] }>>(`${roleType}/role/template`, {
|
||||
method: 'GET'
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const newPermits = generateNewPermit(data.permits)
|
||||
generateDependenciesMap(newPermits)
|
||||
setPermissionTemplate(newPermits)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.dataError))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getPermissionInfo = () => {
|
||||
fetchData<BasicResponse<{ role: PermissionInfo }>>(`${roleType}/role`, {
|
||||
method: 'GET',
|
||||
eoParams: { role: roleId }
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setPermissionInfo(data.role)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errInfo) => console.error(errInfo))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ name: $t(permissionInfo?.name || ''), permits: permissionInfo?.permit })
|
||||
}, [permissionInfo, state.language])
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ name: '', permits: [] })
|
||||
if (roleId) {
|
||||
getPermissionInfo()
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('角色'),
|
||||
onClick: () => navigateTo(-1)
|
||||
},
|
||||
{ title: $t('角色配置') }
|
||||
])
|
||||
getPermissionTemplate()
|
||||
}, [state.language])
|
||||
|
||||
const onFinish = async () => {
|
||||
const body = await form.validateFields()
|
||||
|
||||
return fetchData<BasicResponse<null>>(`${roleType}/role`, {
|
||||
method: roleId === undefined ? 'POST' : 'PUT',
|
||||
eoBody: { ...body },
|
||||
...(roleId !== undefined ? { eoParams: { role: roleId } } : {})
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return Promise.resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errInfo) => Promise.reject(errInfo))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col overflow-hidden ">
|
||||
<TopBreadcrumb handleBackCallback={() => navigateTo(-1)} />
|
||||
<WithPermission
|
||||
access={
|
||||
roleId !== undefined
|
||||
? `system.organization.role.${roleType}.edit`
|
||||
: `system.organization.role.${roleType}.add`
|
||||
}
|
||||
>
|
||||
<Form
|
||||
id="permission"
|
||||
layout="vertical"
|
||||
labelAlign="left"
|
||||
scrollToFirstError
|
||||
form={form}
|
||||
className="mx-auto w-full flex-1 no-bg-form overflow-hidden "
|
||||
name="rolePermissionConfig"
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
<Form.Item
|
||||
className=" m-btnbase mr-PAGE_INSIDE_X"
|
||||
name="name"
|
||||
rules={[{ required: true, whitespace: true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
<Form.Item name="permits" className="m-btnbase mr-0 flex-1 overflow-auto pr-PAGE_INSIDE_X">
|
||||
<PermissionCollapse permissionTemplate={permissionTemplate!} dependenciesMap={dependenciesMap} />
|
||||
</Form.Item>
|
||||
|
||||
{APP_MODE === 'pro' && (
|
||||
<div className="p-btnbase">
|
||||
<WithPermission
|
||||
access={
|
||||
roleId === undefined
|
||||
? `system.organization.role.${roleType}.edit`
|
||||
: `system.organization.role.${roleType}.add`
|
||||
}
|
||||
>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{$t('保存')}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
<Button className="ml-btnrbase" type="default" onClick={() => navigateTo(-1)}>
|
||||
{$t('取消')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default RoleConfig
|
||||
|
||||
@@ -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
|
||||