mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
Compare commits
290 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e010f00a19 | |||
| 1a9b916bab | |||
| 3e4f4e1cff | |||
| 3217ad2bba | |||
| 1485b31226 | |||
| 8968e1f961 | |||
| 4b8fa43c36 | |||
| c21d783c52 | |||
| e928cd84d7 | |||
| 412cb75bf0 | |||
| a13b2a8afe | |||
| c0472c8539 | |||
| 5f40d5092c | |||
| b949939816 | |||
| 55f09f7542 | |||
| ef1ccb81a9 | |||
| 7aad2174aa | |||
| 8fba1911ad | |||
| 2a951c2854 | |||
| e91a9e7726 | |||
| e0d97186b1 | |||
| dff6e722c0 | |||
| b8c92961c1 | |||
| 025bd4c6cc | |||
| 3d2ec67fc1 | |||
| b2a8c8d901 | |||
| 90226ac6af | |||
| 0e1efc9656 | |||
| 061027aa36 | |||
| 96e2e6ad51 | |||
| a083f994f4 | |||
| 5898337481 | |||
| 0f1496137d | |||
| aff2d1ce01 | |||
| 78d10318ad | |||
| 0525c51a8f | |||
| 47950d9a2b | |||
| 4caa19586e | |||
| a574ed8dae | |||
| 8eba1ea898 | |||
| 17bd63b27e | |||
| 88d890107e | |||
| 6b90770a92 | |||
| c1c5eaa84e | |||
| 388cd7660d | |||
| 3df303abc3 | |||
| 217e31787c | |||
| 96d92e2323 | |||
| 6ced12af75 | |||
| 9e7feff093 | |||
| ba6342d207 | |||
| 5b38b2f78a | |||
| 707d98ba34 | |||
| d5f2578a3f | |||
| a8d39b021c | |||
| 78dc382e82 | |||
| 4b58534cb6 | |||
| 63f5a552c8 | |||
| a03e310418 | |||
| 28ad364352 | |||
| e485ae2511 | |||
| 853c04337d | |||
| 526dddb579 | |||
| 902c64903c | |||
| 2f314e0e64 | |||
| 9879506c30 | |||
| 6dabd5d31b | |||
| 16e5a37087 | |||
| 12c5ac85d3 | |||
| 997e9292a2 | |||
| 76ae5287eb | |||
| c9c88116a8 | |||
| 393271d72a | |||
| 1bddef2bda | |||
| 58d02bcf08 | |||
| 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 | |||
| c7c3e8033b | |||
| cc524810e8 | |||
| 4478e6823a | |||
| b6f593c8d0 | |||
| ca54fc581c | |||
| ab5bffea87 | |||
| e7372cb7f2 | |||
| 8a48828a76 | |||
| feac0428ef | |||
| c2b70e23e4 | |||
| c8e5b7541d | |||
| eb46a4365c | |||
| 058a8f7974 | |||
| b5585f548a | |||
| e4a3e1a1a2 | |||
| 674a15ef32 | |||
| 8365f77c67 | |||
| d82d665280 | |||
| 752db42b3b | |||
| 10aaf85a26 | |||
| 5c97ef9416 | |||
| 5fc84299f1 | |||
| 96f675ae9c | |||
| 2eeeebf7c2 | |||
| ae3c189089 | |||
| 155ad537a9 | |||
| 37d421ad8a | |||
| 4a1430c62a | |||
| 8cc0d038bd | |||
| 6c637bf78c | |||
| 165759398e | |||
| 43690156e3 | |||
| 1091d4e086 | |||
| 0523f13dfb | |||
| b703ddaae8 | |||
| 061ea05935 | |||
| 256c04f5bb | |||
| fb31ecc012 | |||
| a9dcc78db6 | |||
| 1f6c173e18 | |||
| 741bdd682c | |||
| 70a8da7682 | |||
| b593e8b57b | |||
| 18615bad33 | |||
| 1ec00de03c | |||
| 8f82436421 | |||
| bad7fbadda | |||
| 7a506fc15e | |||
| dec2c3a23e | |||
| 2093541c37 | |||
| 4beb497032 | |||
| 40c7ba4305 | |||
| a95bca31e2 | |||
| b6115faf7c | |||
| 4fdb677103 | |||
| a541e45a53 | |||
| 34cf8e2b26 | |||
| 93f686af14 | |||
| b20c66b311 | |||
| 48ab6d7c7e | |||
| 729e1f105c | |||
| 08cecd8a0e | |||
| 6aa96a2ae9 | |||
| 79f29254ca | |||
| cc34ca510c | |||
| a285c54be2 | |||
| 5093c98656 | |||
| f4b70d4e71 | |||
| eeb36f43a4 | |||
| 5a59a6d378 | |||
| 4397784749 | |||
| c0045d17e2 | |||
| 42963d3ee5 |
@@ -75,6 +75,11 @@ jobs:
|
||||
with:
|
||||
name: frontend-package
|
||||
path: frontend/dist
|
||||
# 设置 QEMU 以支持多架构构建
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login Docker #登录docker
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
|
||||
+2
-1
@@ -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
|
||||
@@ -156,6 +156,10 @@ curl -sSO https://download.apipark.com/install/quick-start.sh ; bash quick-sta
|
||||
|
||||
<br>
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
|
||||
# 🚀 Use Cases
|
||||
## Simplify AI Integration Costs
|
||||
- Connect to 100+ major models from all mainstream AI vendors, with standardized API calls requiring no additional adaptation work.
|
||||
@@ -199,11 +203,20 @@ To achieve this goal, we plan to add new features to APIPark, including:
|
||||
# 📕 Documentation
|
||||
Visit [APIPark Documentation](https://docs.apipark.com/docs/deploy) for detailed installation guides, API references, and usage instructions.
|
||||
|
||||
|
||||
# 🧑🤝🧑Friendly Links
|
||||
<a href="https://xroute.ai/">
|
||||
<img width="1248" height="158" alt="新建 PPTX 演示文稿 (2)_03" src="https://github.com/user-attachments/assets/0ebd694c-410a-4e3f-a793-90f1140d15df" />
|
||||
|
||||
</a>
|
||||
<br>
|
||||
|
||||
<br>
|
||||
|
||||
# 🧾 License
|
||||
APIPark uses the Apache 2.0 License. For more details, please refer to the LICENSE file.
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
# 💌 Contact Us
|
||||
@@ -212,6 +225,46 @@ For enterprise-level features and professional technical support, contact our pr
|
||||
- Website: https://apipark.com
|
||||
- Email: contact@apipark.com
|
||||
|
||||
🙏 A big thanks to everyone who helped shape APIPark. We are thrilled to hear the community’s thoughts! Let’s make the world of APIs and AI stronger and more fun together. 🎉
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
🙏 A big thanks to everyone who helped shape APIPark. We are thrilled to hear the community’s thoughts! Let’s make the world of APIs and AI stronger and more fun together. 🎉
|
||||
# 🤝 Partner
|
||||
- [Cursor](https://www.cursor.com/): Cursor is an AI-powered code editor that integrates artificial intelligence directly into the coding workflow, offering features like intelligent next edit suggestions, deep codebase understanding for relevant answers, and natural language editing to streamline development tasks and boost developer productivity.
|
||||
|
||||
- [Dify](https://dify.ai/): Dify is a leading Agentic AI Development Platform that provides a comprehensive suite of tools for building and extending AI applications, offering everything needed for agentic workflows, RAG pipelines, integrations, and observability, while allowing users to amplify their applications with various global Large Language Models (LLMs) and versatile plugins.
|
||||
|
||||
- [Trae](https://www.trae.ai/): Trae is an AI-native Integrated Development Environment (IDE) product that aims to embody the concept of “The Real AI Engineer” through intelligent productivity, seamlessly integrating into the development process to enhance quality and efficiency, featuring a chat-based interaction interface and supporting code generation and assistance.
|
||||
|
||||
- [Windsurf](https://windsurf.com/): Windsurf is an AI code editor designed to provide a seamless and limitless flow for developers, introducing a new purpose-built IDE that leverages AI to enhance coding with features like "Cascade" for deep codebase understanding, "Windsurf Tab" for intelligent autocompletion, and "Memories" for remembering important aspects of the codebase.
|
||||
|
||||
- [Coze](https://www.coze.com/): Coze is a next-generation AI application and chatbot development platform by ByteDance, empowering users to easily create and deploy powerful AI chatbots across various platforms with a no-code bot builder, integrated workflow logic, access to proprietary data, and simplified creation through pre-built plugins, knowledge bases, and workflows.
|
||||
|
||||
- [Claude Code](https://www.anthropic.com/claude-code): Claude Code is a command-line AI tool by Anthropic that embeds the Claude Opus 4 model directly into the user’s terminal, providing deep codebase awareness, the ability to edit files and execute commands, and making coordinated changes across multiple files, all while integrating seamlessly with popular IDEs and leveraging existing test suites.
|
||||
|
||||
- [Flowith](https://flowith.io/): Flowith is an AI creation workspace designed to revolutionize productivity and deep work by transforming knowledge and streamlining tasks through a multi-thread interface powered by advanced AI agents, offering an intuitive canvas-based user experience unlike traditional chat-based AI tools, and including a 24/7 operational version for complex tasks.
|
||||
|
||||
- [OpenManus](https://github.com/FoundationAgents/OpenManus): OpenManus is an open-source framework dedicated to building general AI agents, aiming to provide a platform where users can create and deploy their own agents without an invite code, supporting multi-agent capabilities, and requiring configuration for Large Language Model (LLM) APIs while integrating with browser automation tools.
|
||||
|
||||
- [Fellou](https://fellou.ai/): Fellou is an innovative Agentic Browser designed to transcend traditional web browsing by actively performing actions on behalf of the user, automating the entire process of information gathering and insight delivery, and excelling in in-depth research with seamless integrations with popular tools like Notion and LinkedIn.
|
||||
|
||||
- [Genspark](https://www.genspark.ai/): Genspark is an ultimate all-in-one AI companion offering a comprehensive suite of tools like AI Slides, AI Sheets, and AI Chat, designed to enhance various aspects of productivity and content creation, with personalized tools and AI Pods for generating content from diverse sources.
|
||||
|
||||
- [TEN](https://github.com/TEN-framework/ten-framework): TEN (The Embodied Narrator) is an open-source framework for building real-time, multimodal conversational voice AI agents, including components like TEN Framework, TEN Turn Detection, TEN Agent, TMAN Designer, and TEN Portal, offering features like Real-time Avatar, seamless MCP integration, real-time hardware communication, and vision/screenshare detection.
|
||||
|
||||
- [ChatGPT](https://chatgpt.com/): ChatGPT is an AI chatbot developed by OpenAI, built upon large language models like GPT-3.5 and GPT-4, designed to generate human-like conversational dialogue, understand context, answer follow-up questions, and integrate with various platforms for enhanced productivity through advanced language understanding, generation, and multilingual capabilities.
|
||||
|
||||
- [LangChain](https://www.langchain.com/): LangChain is a robust platform engineered for the development of reliable agents and Large Language Model (LLM) applications, offering a comprehensive product suite that seamlessly integrates various tools across the entire application development lifecycle, including LangGraph, LangSmith, and the LangGraph Platform, with functionalities for code generation, automation, and AI Search.
|
||||
|
||||
- [LEMON AI](https://lemonai.cc/): Lemon AI is the first Full-stack, Open-source, Agentic AI framework, offering a fully local alternative to platforms like Manus & Genspark AI. It features an integrated Code Interpreter VM sandbox for safe execution.
|
||||
|
||||
- [LobeChat](https://lobehub.com/): LobeHub offers LobeChat, a personal LLM productivity tool designed to elevate the user experience beyond traditional chatbots by empowering individuals to build personal AI agents and professional teams, supporting a wide array of LLMs, offering a simple chat interface, visual recognition, voice interaction, a rich plugin ecosystem, and knowledge base functionalities.
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/): Visual Studio Code (VS Code) is a widely popular, free, and open-source code editor by Microsoft, renowned for its extensibility and customization, supporting vast programming languages, and integrating AI capabilities like intelligent next edit suggestions and an advanced “agent mode” for complex tasks, with broad compatibility with various AI models.
|
||||
|
||||
- [XRoute](https://xroute.ai): The Unified Interface For LLMs, provides better prices, better throughput, and no subscription.
|
||||
|
||||
- [XPack MCP Marketplace](https://github.com/xpack-ai/XPack-MCP-Marketplace): The world's first open-source MCP monetization platform, transform any OpenAPI into a monetizable MCP server and build your own API marketplace in just 10 minutes. Everything is open-source and ready for commercial use.
|
||||
|
||||
- [MemU](https://github.com/NevaMind-AI/memU): MemU is an open-source memory framework for AI companions
|
||||
|
||||
+62748
-60558
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+155
-50
@@ -6,23 +6,95 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/APIParkLab/APIPark/module/service"
|
||||
|
||||
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:""`
|
||||
mcpModule mcp.IMcpModule `autowired:""`
|
||||
sessionKeys sync.Map
|
||||
server http.Handler
|
||||
openServer http.Handler
|
||||
settingModule system.ISettingModule `autowired:""`
|
||||
authorizationModule application_authorization.IAuthorizationModule `autowired:""`
|
||||
appModule service.IAppModule `autowired:""`
|
||||
mcpModule mcp.IMcpModule `autowired:""`
|
||||
sessionKeys sync.Map
|
||||
server map[string]http.Handler
|
||||
openServer http.Handler
|
||||
}
|
||||
|
||||
func (i *imlMcpController) AppMCPHandle(ctx *gin.Context) {
|
||||
appId := ctx.Param("app")
|
||||
if appId == "" {
|
||||
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid app id", "success": "fail"})
|
||||
return
|
||||
}
|
||||
cfg := i.settingModule.Get(ctx)
|
||||
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
|
||||
req = req.WithContext(utils.SetLabel(req.Context(), "app", appId))
|
||||
paths := strings.Split(req.URL.Path, "/")
|
||||
req.URL.Path = fmt.Sprintf("/api/v1/%s/%s", mcp_server.GlobalBasePath, paths[len(paths)-1])
|
||||
locale := utils.I18n(ctx)
|
||||
if v, ok := i.server[locale]; ok {
|
||||
v.ServeHTTP(ctx.Writer, req)
|
||||
return
|
||||
}
|
||||
|
||||
i.server[languageEnUs].ServeHTTP(ctx.Writer, req)
|
||||
}
|
||||
|
||||
func (i *imlMcpController) AppHandleSSE(ctx *gin.Context) {
|
||||
apikey := ctx.Request.URL.Query().Get("apikey")
|
||||
appId := ctx.Param("app")
|
||||
if appId == "" {
|
||||
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid app id", "success": "fail"})
|
||||
return
|
||||
}
|
||||
ok, err := i.authorizationModule.CheckAPIKeyAuthorizationByApp(ctx, appId, 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
|
||||
}
|
||||
|
||||
ctx.Request.URL.Path = fmt.Sprintf("/openapi/v1/%s/sse", mcp_server.GlobalBasePath)
|
||||
i.handleSSE(ctx, i.openServer, SessionInfo{
|
||||
Apikey: apikey,
|
||||
App: appId,
|
||||
})
|
||||
}
|
||||
|
||||
func (i *imlMcpController) AppHandleMessage(ctx *gin.Context) {
|
||||
appId := ctx.Param("app")
|
||||
if appId == "" {
|
||||
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid app id", "success": "fail"})
|
||||
return
|
||||
}
|
||||
ctx.Request.URL.Path = fmt.Sprintf("/openapi/v1/%s/message", mcp_server.GlobalBasePath)
|
||||
ctx.Request = ctx.Request.WithContext(utils.SetLabel(ctx.Request.Context(), "app", appId))
|
||||
i.handleMessage(ctx, i.openServer)
|
||||
}
|
||||
|
||||
func (i *imlMcpController) AppMCPConfig(ctx *gin.Context, appId string) (string, error) {
|
||||
cfg := i.settingModule.Get(ctx)
|
||||
if cfg.SitePrefix == "" {
|
||||
return "", fmt.Errorf("site prefix is empty")
|
||||
}
|
||||
appInfo, err := i.appModule.GetApp(ctx, appId)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get app info error: %v", err)
|
||||
}
|
||||
return fmt.Sprintf(mcpDefaultConfig, appInfo.Name, fmt.Sprintf("%s/openapi/v1/mcp/app/%s/sse?apikey={your_api_key}", strings.TrimSuffix(cfg.SitePrefix, "/"), appId)), nil
|
||||
}
|
||||
|
||||
var mcpDefaultConfig = `{
|
||||
@@ -43,53 +115,39 @@ func (i *imlMcpController) GlobalMCPConfig(ctx *gin.Context) (string, error) {
|
||||
}
|
||||
|
||||
func (i *imlMcpController) OnComplete() {
|
||||
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"apipark_service_list",
|
||||
mcp2.WithDescription("This tool is a standardized interface provided by the Apipark platform under the MCP (Model Context Protocol) framework, designed to retrieve metadata for all registered services in bulk. By invoking this tool, users can efficiently explore the complete list of published services and their core attributes, serving as a prerequisite for subsequent actions such as querying detailed API lists via service IDs, requesting access permissions, or integrating services."),
|
||||
mcp2.WithString("keyword", mcp2.Description("Keyword for fuzzy search")),
|
||||
),
|
||||
i.mcpModule.Services,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"apipark_service_api_list",
|
||||
mcp2.WithDescription("This tool is a standardized MCP (Model Context Protocol) interface provided by the Apipark platform, designed to retrieve OpenAPI specification documents for all APIs under a specified service using its service ID. By invoking this tool, users gain precise access to detailed API definitions (including endpoints, parameters, request/response schemas) for debugging, integration, or client SDK generation."),
|
||||
mcp2.WithString("service", mcp2.Description("Service ID")),
|
||||
),
|
||||
i.mcpModule.APIs,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"apipark_invoke_api",
|
||||
mcp2.WithDescription("This tool is a core MCP (Model Context Protocol) interface provided by the Apipark platform, enabling users to programmatically invoke APIs using metadata from apipark_service_api_list (API schemas). It acts as a unified gateway for executing API requests with built-in authentication, parameter validation, and error handling, returning structured responses for integration workflows."),
|
||||
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,
|
||||
)
|
||||
i.server = server.NewSSEServer(s, server.WithBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
||||
i.openServer = server.NewSSEServer(s, server.WithBasePath(fmt.Sprintf("/openapi/v1/%s", strings.Trim(mcp_server.GlobalBasePath, "/"))))
|
||||
i.server = make(map[string]http.Handler)
|
||||
for language, tools := range mcpToolsByLanguage {
|
||||
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
|
||||
s.AddTool(tools[ToolServiceList], i.mcpModule.Services)
|
||||
s.AddTool(tools[ToolOpenAPIDocument], i.mcpModule.APIs)
|
||||
s.AddTool(tools[ToolInvokeAPI], i.mcpModule.Invoke)
|
||||
i.server[language] = server.NewSSEServer(s, server.WithStaticBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
||||
if language == languageEnUs {
|
||||
i.openServer = server.NewSSEServer(s, server.WithStaticBasePath(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))
|
||||
|
||||
i.server.ServeHTTP(ctx.Writer, req)
|
||||
locale := utils.I18n(ctx)
|
||||
if v, ok := i.server[locale]; ok {
|
||||
v.ServeHTTP(ctx.Writer, req)
|
||||
return
|
||||
}
|
||||
i.server[languageEnUs].ServeHTTP(ctx.Writer, req)
|
||||
}
|
||||
|
||||
func (i *imlMcpController) GlobalHandleSSE(ctx *gin.Context) {
|
||||
i.handleSSE(ctx, i.openServer)
|
||||
apikey := ctx.Request.URL.Query().Get("apikey")
|
||||
i.handleSSE(ctx, i.openServer, SessionInfo{
|
||||
Apikey: apikey,
|
||||
})
|
||||
}
|
||||
|
||||
func (i *imlMcpController) handleSSE(ctx *gin.Context, server http.Handler) {
|
||||
apikey := ctx.Request.URL.Query().Get("apikey")
|
||||
func (i *imlMcpController) handleSSE(ctx *gin.Context, server http.Handler, sIn SessionInfo) {
|
||||
|
||||
writer := &ResponseWriter{
|
||||
Writer: ctx.Writer,
|
||||
sessionId: make(chan string),
|
||||
@@ -102,7 +160,7 @@ func (i *imlMcpController) handleSSE(ctx *gin.Context, server http.Handler) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
i.sessionKeys.Store(sessionId, apikey)
|
||||
i.sessionKeys.Store(sessionId, sIn)
|
||||
}()
|
||||
server.ServeHTTP(writer, ctx.Request)
|
||||
i.sessionKeys.Delete(sessionId)
|
||||
@@ -120,22 +178,69 @@ func (i *imlMcpController) MCPHandle(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
func (i *imlMcpController) ServiceHandleSSE(ctx *gin.Context) {
|
||||
i.handleSSE(ctx, mcp_server.DefaultMCPServer())
|
||||
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.CheckAPIKeyAuthorizationByService(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(), SessionInfo{
|
||||
Apikey: 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)
|
||||
func (i *imlMcpController) ServiceHandleStreamHTTP(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.CheckAPIKeyAuthorizationByService(ctx, serviceId, apikey)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": err.Error(), "success": "fail"})
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
ctx.String(403, "sessionId not found")
|
||||
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid apikey", "success": "fail"})
|
||||
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)))
|
||||
req = req.WithContext(utils.SetLabel(req.Context(), "apikey", apikey))
|
||||
mcp_server.DefaultMCPServer().ServeHTTP(ctx.Writer, req)
|
||||
}
|
||||
|
||||
func (i *imlMcpController) handleMessage(ctx *gin.Context, server http.Handler) {
|
||||
sessionId := ctx.Request.URL.Query().Get("sessionId")
|
||||
params, ok := i.sessionKeys.Load(sessionId)
|
||||
if !ok {
|
||||
ctx.String(403, "sessionId not found")
|
||||
return
|
||||
}
|
||||
ps, ok := params.(SessionInfo)
|
||||
cfg := i.settingModule.Get(ctx)
|
||||
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
|
||||
req = req.WithContext(utils.SetLabel(req.Context(), "apikey", ps.Apikey))
|
||||
req = req.WithContext(utils.SetLabel(req.Context(), "app", ps.App))
|
||||
server.ServeHTTP(ctx.Writer, req)
|
||||
}
|
||||
|
||||
type SessionInfo struct {
|
||||
Apikey string
|
||||
App string
|
||||
}
|
||||
|
||||
+10
-1
@@ -9,12 +9,21 @@ import (
|
||||
|
||||
type IMcpController interface {
|
||||
MCPHandle(ctx *gin.Context)
|
||||
|
||||
GlobalMCPHandle(ctx *gin.Context)
|
||||
GlobalHandleSSE(ctx *gin.Context)
|
||||
GlobalHandleMessage(ctx *gin.Context)
|
||||
GlobalMCPConfig(ctx *gin.Context) (string, error)
|
||||
|
||||
AppMCPHandle(ctx *gin.Context)
|
||||
AppHandleSSE(ctx *gin.Context)
|
||||
AppHandleMessage(ctx *gin.Context)
|
||||
AppMCPConfig(ctx *gin.Context, appId string) (string, error)
|
||||
|
||||
ServiceHandleSSE(ctx *gin.Context)
|
||||
ServiceHandleMessage(ctx *gin.Context)
|
||||
GlobalMCPConfig(ctx *gin.Context) (string, error)
|
||||
|
||||
ServiceHandleStreamHTTP(ctx *gin.Context)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package mcp
|
||||
|
||||
import "github.com/mark3labs/mcp-go/mcp"
|
||||
|
||||
const (
|
||||
ToolServiceList = "service_list"
|
||||
ToolOpenAPIDocument = "openapi_document"
|
||||
ToolInvokeAPI = "invoke_api"
|
||||
|
||||
languageZhCN = "zh-CN"
|
||||
languageZhTW = "zh-TW"
|
||||
languageEnUs = "en-US"
|
||||
languageJaJp = "ja-JP"
|
||||
)
|
||||
|
||||
var mcpToolsByLanguage = map[string]map[string]mcp.Tool{
|
||||
languageZhCN: toolsZhCN,
|
||||
languageZhTW: toolsZhTW,
|
||||
languageEnUs: toolsEnUs,
|
||||
languageJaJp: toolsJaJp,
|
||||
}
|
||||
|
||||
var toolsZhTW = map[string]mcp.Tool{
|
||||
ToolServiceList: mcp.NewTool(
|
||||
ToolServiceList,
|
||||
mcp.WithDescription("此工具用於獲取 APIPark 中已註冊服務的列表。每個服務包含其唯一標識(service ID)、名稱、描述及包含的 API 列表等關鍵信息。支持通過關鍵詞進行模糊搜索,以便快速縮小查找範圍。在獲得某個服務的 ID 後,可以調用 openapi_document 工具來獲取該服務的 OpenAPI 文檔,以便後續調用其提供的 API 接口。"),
|
||||
mcp.WithString("keyword", mcp.Description("關鍵詞,用於模糊搜索服務"))),
|
||||
ToolOpenAPIDocument: mcp.NewTool(
|
||||
ToolOpenAPIDocument,
|
||||
mcp.WithDescription("此工具用於獲取 APIPark 中服務的 OpenAPI 文檔。"),
|
||||
mcp.WithString("service_id", mcp.Description("服務 ID"))),
|
||||
ToolInvokeAPI: mcp.NewTool(
|
||||
ToolInvokeAPI,
|
||||
mcp.WithDescription("此工具可直接發送 API 請求。在調用此工具之前,需要根據該 API 的 OpenAPI 文檔構造所需的請求參數,如請求路徑、方法、查詢參數、頭部信息、主體內容等。使用此工具時不需要傳遞任何認證信息,例如 Authorization 頭部可以省略。"),
|
||||
mcp.WithString("path", mcp.Description("API 的請求路徑"), mcp.Required()),
|
||||
mcp.WithString("method", mcp.Description("API 的請求方法,例如 GET、POST、PUT"), mcp.Required()),
|
||||
mcp.WithString("content-type", mcp.Description("請求的 Content-Type。若方法為 POST、PUT 或 PATCH,則必須指定")),
|
||||
mcp.WithObject("query", mcp.Description("請求的查詢參數,類型為 map[string]string")),
|
||||
mcp.WithObject("header", mcp.Description("請求的頭部,類型為 map[string]string")),
|
||||
mcp.WithString("body", mcp.Description("請求主體內容,通常為 JSON 字符串")),
|
||||
),
|
||||
}
|
||||
|
||||
var toolsZhCN = map[string]mcp.Tool{
|
||||
ToolServiceList: mcp.NewTool(
|
||||
ToolServiceList,
|
||||
mcp.WithDescription("此工具用于获取 APIPark 中已注册服务的列表。每个服务包含其唯一标识(service ID)、名称、描述及包含的 API 列表等关键信息。支持通过关键词进行模糊搜索,以便快速缩小查找范围。在获得某个服务的 ID 后,可以调用 openapi_document 工具来获取该服务的 OpenAPI 文档,以便后续调用其提供的 API 接口。"),
|
||||
mcp.WithString("keyword", mcp.Description("关键词,用于模糊搜索服务"))),
|
||||
ToolOpenAPIDocument: mcp.NewTool(
|
||||
ToolOpenAPIDocument, mcp.WithDescription("此工具用于查询指定服务的 OpenAPI 文档。返回的格式支持 OpenAPI v3 和 v2 标准。通过输入服务 ID,可以查看该服务所有 API 的定义、参数结构、请求方式等详细信息,为后续构造 API 调用请求做准备。"),
|
||||
mcp.WithString("service", mcp.Description("欲查询的服务唯一标识")),
|
||||
),
|
||||
ToolInvokeAPI: mcp.NewTool(ToolInvokeAPI,
|
||||
mcp.WithDescription("此工具可直接发送 API 请求。在调用此工具之前,需要根据该 API 的 OpenAPI 文档构造所需的请求参数,如请求路径、方法、查询参数、头部信息、主体内容等。使用此工具时不需要传递任何认证信息,例如 Authorization 头部可以省略。"),
|
||||
mcp.WithString("path", mcp.Description("API 的请求路径"), mcp.Required()),
|
||||
mcp.WithString("method", mcp.Description("API 的请求方法,例如 GET、POST、PUT"), mcp.Required()),
|
||||
mcp.WithString("content-type", mcp.Description("请求的 Content-Type。若方法为 POST、PUT 或 PATCH,则必须指定")),
|
||||
mcp.WithObject("query", mcp.Description("请求的查询参数,类型为 map[string]string")),
|
||||
mcp.WithObject("header", mcp.Description("请求的头部,类型为 map[string]string")),
|
||||
mcp.WithString("body", mcp.Description("请求主体内容,通常为 JSON 字符串")),
|
||||
),
|
||||
}
|
||||
|
||||
var toolsEnUs = map[string]mcp.Tool{
|
||||
ToolServiceList: mcp.NewTool(
|
||||
ToolServiceList,
|
||||
mcp.WithDescription("This tool is used to retrieve a list of registered services in APIPark. Each service includes its unique identifier (service ID), name, description, and a list of APIs it contains. It supports fuzzy searching by keyword for quick narrowing down of results. After obtaining a service ID, you can use the openapi_document tool to get the OpenAPI documentation for that service, which is necessary for invoking its APIs."),
|
||||
mcp.WithString("keyword", mcp.Description("Keyword for fuzzy search of services"))),
|
||||
ToolOpenAPIDocument: mcp.NewTool(
|
||||
ToolOpenAPIDocument,
|
||||
mcp.WithDescription("This tool is used to query the OpenAPI documentation of a specified service. The returned format supports both OpenAPI v3 and v2 standards. By entering the service ID, you can view detailed information about all APIs of that service, including definitions, parameter structures, request methods, etc., which prepares you for subsequent API calls."),
|
||||
mcp.WithString("service", mcp.Description("Unique identifier of the service to query"))),
|
||||
ToolInvokeAPI: mcp.NewTool(
|
||||
ToolInvokeAPI,
|
||||
mcp.WithDescription("This tool can directly send API requests. Before using this tool, you need to construct the required request parameters based on the OpenAPI documentation of the API, such as request path, method, query parameters, header information, body content, etc. No authentication information like Authorization header is required when using this tool."),
|
||||
mcp.WithString("path", mcp.Description("API request path"), mcp.Required()),
|
||||
mcp.WithString("method", mcp.Description("API request method, e.g., GET, POST, PUT"), mcp.Required()),
|
||||
mcp.WithString("content-type", mcp.Description("Content-Type of the request. Must be specified if method is POST, PUT, or PATCH")),
|
||||
mcp.WithObject("query", mcp.Description("Query parameters of the request, type map[string]string")),
|
||||
mcp.WithObject("header", mcp.Description("Header information of the request, type map[string]string")),
|
||||
mcp.WithString("body", mcp.Description("Body content of the request, usually in JSON string")),
|
||||
),
|
||||
}
|
||||
|
||||
var toolsJaJp = map[string]mcp.Tool{
|
||||
ToolServiceList: mcp.NewTool(
|
||||
ToolServiceList,
|
||||
mcp.WithDescription("このツールは、APIParkに登録されているサービスのリストを取得するために使用されます。各サービスには、ユニークな識別子(サービスID)、名前、説明、および含まれるAPIのリストが含まれています。キーワードによるあいまい検索をサポートしており、結果を迅速に絞り込むことができます。サービスIDを取得した後は、openapi_documentツールを使用してそのサービスのOpenAPIドキュメントを取得し、そのAPIを呼び出す準備をします。"),
|
||||
mcp.WithString("keyword", mcp.Description("サービスをあいまい検索するためのキーワード"))),
|
||||
ToolOpenAPIDocument: mcp.NewTool(
|
||||
ToolOpenAPIDocument,
|
||||
mcp.WithDescription("このツールは、指定されたサービスのOpenAPIドキュメントを照会するために使用されます。返される形式は、OpenAPI v3およびv2標準の両方をサポートしています。サービスIDを入力することで、そのサービスのすべてのAPIに関する詳細情報(定義、パラメータ構造、リクエスト方法など)を表示し、後続のAPI呼び出しの準備をします。"),
|
||||
mcp.WithString("service", mcp.Description("照会するサービスのユニークな識別子"))),
|
||||
ToolInvokeAPI: mcp.NewTool(
|
||||
ToolInvokeAPI,
|
||||
mcp.WithDescription("このツールは、APIリクエストを直接送信できます。このツールを使用する前に、APIのOpenAPIドキュメントに基づいて、必要なリクエストパラメータ(リクエストパス、メソッド、クエリパラメータ、ヘッダー情報、ボディコンテンツなど)を構築する必要があります。このツールを使用する際には、Authorizationヘッダーなどの認証情報は必要ありません。"),
|
||||
mcp.WithString("path", mcp.Description("APIのリクエストパス"), mcp.Required()),
|
||||
mcp.WithString("method", mcp.Description("APIのリクエストメソッド(例:GET、POST、PUT)"), mcp.Required()),
|
||||
mcp.WithString("content-type", mcp.Description("リクエストのContent-Type。メソッドがPOST、PUT、またはPATCHの場合は必須です")),
|
||||
mcp.WithObject("query", mcp.Description("リクエストのクエリパラメータ、タイプはmap[string]string")),
|
||||
mcp.WithObject("header", mcp.Description("リクエストのヘッダー、タイプはmap[string]string")),
|
||||
mcp.WithString("body", mcp.Description("リクエストのボディコンテンツ、通常はJSON文字列")),
|
||||
),
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
+211
-20
@@ -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 {
|
||||
@@ -542,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
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ type IAPIKeyController interface {
|
||||
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, appId string) ([]*system_apikey_dto.AuthorizationItem, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -12,6 +12,16 @@ type imlAPIKeyController struct {
|
||||
apikeyModule system_apikey.IAPIKeyModule `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlAPIKeyController) MyAPIKeysByService(ctx *gin.Context, serviceId string, appId string) ([]*system_apikey_dto.AuthorizationItem, error) {
|
||||
if serviceId != "" {
|
||||
return i.apikeyModule.MyAPIKeysByService(ctx, serviceId)
|
||||
}
|
||||
if appId != "" {
|
||||
return i.apikeyModule.MyAPIKeysByApp(ctx, appId)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (i *imlAPIKeyController) MyAPIKeys(ctx *gin.Context) ([]*system_apikey_dto.SimpleItem, error) {
|
||||
return i.apikeyModule.MyAPIKeys(ctx)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=core --stream --verbose ",
|
||||
"build": "set NODE_OPTIONS=--max-old-space-size=8192 && lerna run build --scope=core --stream --verbose ",
|
||||
"serve": "lerna run preview --parallel",
|
||||
"serve:remotes": "lerna run serve --scope=remote --parallel",
|
||||
"dev": "lerna run dev --scope=core --stream",
|
||||
@@ -50,7 +50,8 @@
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"react-json-view": "^1.21.3",
|
||||
"zod": "^3.23.8",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0"
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"echarts-for-react": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/cssinjs": "^1.18.2",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
@@ -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()
|
||||
|
||||
@@ -137,7 +137,7 @@ function BasicLayout({ project = 'core' }: { project: string }) {
|
||||
const items: MenuProps['items'] = useMemo(
|
||||
() =>
|
||||
[
|
||||
userInfo?.type !== 'guest' && {
|
||||
!['guest', 'third-user'].includes(userInfo?.type as string) && {
|
||||
key: '2',
|
||||
label: (
|
||||
<Button
|
||||
|
||||
@@ -26,6 +26,7 @@ class InsidePageProps {
|
||||
scrollInsidePage?: boolean = false
|
||||
customPadding?: boolean
|
||||
customBtn?: ReactNode
|
||||
customBanner?: ReactNode
|
||||
}
|
||||
|
||||
const InsidePage: FC<InsidePageProps> = ({
|
||||
@@ -46,7 +47,8 @@ const InsidePage: FC<InsidePageProps> = ({
|
||||
scrollPage = true,
|
||||
scrollInsidePage = false,
|
||||
customPadding = false,
|
||||
customBtn
|
||||
customBtn,
|
||||
customBanner
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -61,8 +63,13 @@ const InsidePage: FC<InsidePageProps> = ({
|
||||
<div
|
||||
className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-solid border-b-[1px] border-BORDER' : ''} ${headerClassName}`}
|
||||
>
|
||||
{!pageTitle && !description && !backUrl && !customBtn ? (
|
||||
{!pageTitle && !description && !backUrl && !customBtn && !customBanner ? (
|
||||
<></>
|
||||
) : customBanner ? (
|
||||
<div className={customPadding ? '' : 'mb-[15px]'}>
|
||||
{backUrl && <TopBreadcrumb handleBackCallback={() => goBack()} />}
|
||||
{customBanner}
|
||||
</div>
|
||||
) : (
|
||||
<div className={customPadding ? '' : 'mb-[30px]'}>
|
||||
{backUrl && <TopBreadcrumb handleBackCallback={() => goBack()} />}
|
||||
|
||||
@@ -58,7 +58,7 @@ interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<Acti
|
||||
delayLoading?: boolean
|
||||
noScroll?: boolean
|
||||
/* 前端分页的表格,需要传入该字段以支持后端搜索 */
|
||||
manualReloadTable?: () => void,
|
||||
manualReloadTable?: () => void
|
||||
customEmptyRender?: () => React.ReactNode
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
const [allowTableClick, setAllowTableClick] = useState<boolean>(false)
|
||||
const { accessData, checkPermission, accessInit, state } = useGlobalContext()
|
||||
const [minTableWidth, setMinTableWidth] = useState<number>(0)
|
||||
const [enableVirtual, setEnableVirtual] = useState(false)
|
||||
|
||||
useImperativeHandle(ref, () => actionRef.current!)
|
||||
|
||||
@@ -301,7 +302,7 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
<ProTable<T>
|
||||
actionRef={actionRef}
|
||||
columns={newColumns}
|
||||
virtual
|
||||
virtual={enableVirtual}
|
||||
scroll={noScroll ? undefined : { x: tableWidth, y: tableHeight }}
|
||||
size="middle"
|
||||
rowSelection={rowSelection}
|
||||
@@ -328,6 +329,10 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
}
|
||||
: false
|
||||
}}
|
||||
postData={(data: any) => {
|
||||
setEnableVirtual(!!data?.length)
|
||||
return data
|
||||
}}
|
||||
showSorterTooltip={false}
|
||||
columnsState={{ persistenceType: 'localStorage', persistenceKey: id }}
|
||||
pagination={
|
||||
|
||||
@@ -27,6 +27,7 @@ type TimeRangeSelectorProps = {
|
||||
bindRef?: any
|
||||
hideBtns?: TimeRangeButton[]
|
||||
defaultTimeButton?: TimeRangeButton
|
||||
customClassNames?: string
|
||||
}
|
||||
const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
const {
|
||||
@@ -38,7 +39,8 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
labelSize = 'default',
|
||||
bindRef,
|
||||
hideBtns = [],
|
||||
defaultTimeButton = 'hour'
|
||||
defaultTimeButton = 'hour',
|
||||
customClassNames = 'pt-btnybase'
|
||||
} = props
|
||||
const [timeButton, setTimeButton] = useState(initialTimeButton || '')
|
||||
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null, null])
|
||||
@@ -110,8 +112,12 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
return current && current.valueOf() > dayjs().startOf('day').valueOf()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setTimeButton(initialTimeButton || '')
|
||||
}, [initialTimeButton])
|
||||
|
||||
return (
|
||||
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
|
||||
<div className={`flex flex-nowrap items-center ${customClassNames} mr-btnybase`}>
|
||||
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}:</label>}
|
||||
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
|
||||
{hideBtns?.length && hideBtns.includes('hour') ? null : (
|
||||
|
||||
@@ -187,6 +187,9 @@ export const TranslateWord = () => {
|
||||
{$t('调用地址')}
|
||||
{$t('消费者 IP')}
|
||||
{$t('鉴权名称')}
|
||||
{$t('日志输出')}
|
||||
{$t('响应时间')}
|
||||
{$t('时间戳')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
import { Avatar, Button, Card, Tag, Tooltip, App } from 'antd'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { ApiOutlined } from '@ant-design/icons'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { SERVICE_KIND_OPTIONS } from '@core/const/system/const'
|
||||
import { IconButton } from '@common/components/postcat/api/IconButton'
|
||||
import useCopyToClipboard from '@common/hooks/copy'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
|
||||
export type ServiceBasicInfoType = {
|
||||
id?: string
|
||||
logo?: string
|
||||
name: string
|
||||
description: string
|
||||
appNum: number
|
||||
apiNum: number
|
||||
serviceName: string
|
||||
serviceDesc: string
|
||||
invokeCount: number
|
||||
catalogue: {
|
||||
name: string
|
||||
}
|
||||
serviceKind: string
|
||||
service_kind: string
|
||||
enableMcp: boolean
|
||||
enable_mcp: boolean
|
||||
isReleased?: boolean
|
||||
}
|
||||
|
||||
type ServiceInfoCardProps = {
|
||||
actionSlot?: React.ReactNode
|
||||
customClassName?: string
|
||||
serviceId?: string
|
||||
serviceBasicInfo?: ServiceBasicInfoType
|
||||
teamId?: string
|
||||
}
|
||||
const ServiceInfoCard = ({
|
||||
actionSlot,
|
||||
customClassName,
|
||||
serviceId,
|
||||
serviceBasicInfo,
|
||||
teamId
|
||||
}: ServiceInfoCardProps) => {
|
||||
/** 服务指标 */
|
||||
const [serviceMetrics, setServiceMetrics] = useState<{ title: string; icon: React.ReactNode; value: string }[]>([])
|
||||
/** 服务标签 */
|
||||
const [serviceTags, setServiceTags] = useState<
|
||||
{ color: string; textColor: string; title: string; content: React.ReactNode }[]
|
||||
>([])
|
||||
/** 剪切板 */
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
/** 弹窗组件 */
|
||||
const { message } = App.useApp()
|
||||
/** 获取服务信息 */
|
||||
const { fetchData } = useFetch()
|
||||
/** 服务信息 */
|
||||
const [serviceOverview, setServiceOverview] = useState<ServiceBasicInfoType>()
|
||||
|
||||
/**
|
||||
* 复制
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
const handleCopy = async (value: string): Promise<void> => {
|
||||
if (value) {
|
||||
copyToClipboard(value)
|
||||
message.success($t(RESPONSE_TIPS.copySuccess))
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取服务信息 */
|
||||
const getServiceOverview = () => {
|
||||
fetchData<BasicResponse<{ overview: ServiceBasicInfoType }>>('service/overview/basic', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId, team: teamId },
|
||||
eoTransformKeys: [
|
||||
'api_num',
|
||||
'enable_mcp',
|
||||
'service_kind',
|
||||
'subscriber_num',
|
||||
'invoke_num',
|
||||
'avaliable_monitor',
|
||||
'is_released'
|
||||
]
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const serviceOverview = {
|
||||
...data.overview,
|
||||
appNum: data.overview.subscriberNum,
|
||||
invokeCount: data.overview.invokeNum,
|
||||
serviceName: data.overview.name,
|
||||
serviceDesc: data.overview.description
|
||||
}
|
||||
setServiceOverview(serviceOverview)
|
||||
setServiceMetricsList(serviceOverview)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开服务详情页面
|
||||
*/
|
||||
const openInPortal = () => {
|
||||
window.open(`/portal/detail/${serviceOverview?.id}`, '_blank')
|
||||
}
|
||||
|
||||
// 格式化调用次数,添加K和M单位
|
||||
const formatInvokeCount = (count: number | null | undefined): string => {
|
||||
if (count === null || count === undefined) return '-'
|
||||
if (count >= 1000000) {
|
||||
const value = Math.floor(count / 100000) / 10
|
||||
return `${value}M`
|
||||
}
|
||||
if (count >= 1000) {
|
||||
const value = Math.floor(count / 100) / 10
|
||||
return `${value}K`
|
||||
}
|
||||
return count.toString()
|
||||
}
|
||||
|
||||
const setServiceMetricsList = (serviceOverview: ServiceBasicInfoType) => {
|
||||
// 设置服务指标数据
|
||||
setServiceMetrics([
|
||||
{
|
||||
title: 'API 数量',
|
||||
icon: <ApiOutlined className="mr-[1px] text-[14px] h-[14px] w-[14px]" />,
|
||||
value: serviceOverview.apiNum?.toString() || '0'
|
||||
},
|
||||
{
|
||||
title: '接入消费者数量',
|
||||
icon: <Icon icon="tabler:api-app" width="14" height="14" />,
|
||||
value: serviceOverview.appNum?.toString() || '0'
|
||||
},
|
||||
{
|
||||
title: '30天内调用次数',
|
||||
icon: <Icon icon="iconoir:graph-up" width="14" height="14" />,
|
||||
value: formatInvokeCount(serviceOverview.invokeCount ?? 0)
|
||||
}
|
||||
])
|
||||
const serviceKind = serviceOverview?.serviceKind || serviceOverview?.service_kind
|
||||
// 设置服务标签数据
|
||||
const tags = [
|
||||
{
|
||||
color: '#7371fc1b',
|
||||
textColor: 'text-theme',
|
||||
title: serviceOverview?.catalogue?.name || '-',
|
||||
content: serviceOverview?.catalogue?.name || '-'
|
||||
},
|
||||
{
|
||||
color: `#${serviceKind === 'ai' ? 'EADEFF' : 'DEFFE7'}`,
|
||||
textColor: 'text-[#000]',
|
||||
title: serviceKind || '-',
|
||||
content: SERVICE_KIND_OPTIONS.find((x) => x.value === serviceKind)?.label || '-'
|
||||
}
|
||||
]
|
||||
|
||||
// 如果启用了MCP,添加MCP标签
|
||||
if (serviceOverview?.enableMcp) {
|
||||
tags.push({
|
||||
color: '#FFF0C1',
|
||||
textColor: 'text-[#000]',
|
||||
title: 'MCP',
|
||||
content: 'MCP'
|
||||
})
|
||||
}
|
||||
|
||||
setServiceTags(tags)
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!serviceId && serviceBasicInfo) {
|
||||
setServiceMetricsList(serviceBasicInfo)
|
||||
setServiceOverview(serviceBasicInfo)
|
||||
return
|
||||
}
|
||||
getServiceOverview()
|
||||
}, [serviceId, serviceBasicInfo])
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
style={{
|
||||
borderRadius: '10px',
|
||||
background: 'linear-gradient(35deg, rgb(246, 246, 260) 0%, rgb(255, 255, 255) 40%)'
|
||||
}}
|
||||
className={`w-full ${customClassName}`}
|
||||
classNames={{
|
||||
body: `p-[15px] ${actionSlot ? 'h-[180px]' : 'max-h-[130px]'}`
|
||||
}}
|
||||
>
|
||||
{serviceOverview && (
|
||||
<>
|
||||
<div className="service-info">
|
||||
<div className="flex items-center">
|
||||
<div>
|
||||
<Avatar
|
||||
shape="square"
|
||||
size={50}
|
||||
className={`rounded-[12px] border-none rounded-[12px] ${serviceOverview.logo ? 'bg-[linear-gradient(135deg,white,#f0f0f0)]' : 'bg-theme'}`}
|
||||
src={
|
||||
serviceOverview.logo ? (
|
||||
<img
|
||||
src={serviceOverview.logo}
|
||||
alt="Logo"
|
||||
style={{ maxWidth: '200px', width: '45px', height: '45px', objectFit: 'unset' }}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
icon={serviceOverview.logo ? '' : <Icon icon="tabler:api-app" />}
|
||||
>
|
||||
{' '}
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="pl-[20px] w-[calc(100%-50px)] overflow-hidden">
|
||||
<p
|
||||
className={`text-[14px] h-[20px] leading-[20px] truncate font-bold w-full flex items-center gap-[4px]`}
|
||||
>
|
||||
{serviceOverview.serviceName}
|
||||
</p>
|
||||
<div className="mt-[5px] h-[20px] flex items-center font-normal">
|
||||
{serviceTags.map((tag, index) => (
|
||||
<Tag
|
||||
key={index}
|
||||
color={tag.color}
|
||||
className={`${tag.textColor} font-normal border-0 mr-[12px] max-w-[150px] truncate`}
|
||||
bordered={false}
|
||||
title={tag.title}
|
||||
>
|
||||
{tag.content}
|
||||
</Tag>
|
||||
))}
|
||||
{serviceMetrics.map((item, index) => (
|
||||
<Tooltip key={index} title={$t(item.title)}>
|
||||
<span className="mr-[12px] flex items-center">
|
||||
<span className="h-[14px] mr-[4px] flex items-center">{item.icon}</span>
|
||||
<span className="font-normal text-[14px]">{item.value}</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{serviceOverview.id && (
|
||||
<>
|
||||
<div className="absolute top-[14px] right-[20px]">
|
||||
<span className="bg-white relative py-[2px] pl-[10px] pr-[30px] inline-block border-solid border-[1px] border-BORDER rounded-lg">
|
||||
{$t('服务 ID')}:{serviceOverview.id || '-'}
|
||||
<IconButton
|
||||
name="copy"
|
||||
onClick={() => handleCopy(serviceOverview.id || '')}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
right: '5px',
|
||||
color: '#999',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
</span>
|
||||
<Tooltip title={serviceOverview.isReleased ? '' : $t('服务尚未发布')}>
|
||||
<Button
|
||||
disabled={!serviceOverview.isReleased}
|
||||
className="ml-[10px] !max-h-[28px] rounded-[13px]"
|
||||
type="primary"
|
||||
onClick={() => openInPortal()}
|
||||
>
|
||||
{$t('跳转至详情页')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<span className="line-clamp-2 mt-[15px] text-[12px] text-[#666]" title={serviceOverview.serviceDesc}>
|
||||
{serviceOverview.serviceDesc || $t('暂无服务描述')}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="absolute bottom-[15px]">{actionSlot}</div>
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceInfoCard
|
||||
@@ -0,0 +1,431 @@
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
}(this, function (exports, echarts) {
|
||||
var log = function (msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
echarts.registerTheme('apipark chart palette', {
|
||||
"color": [
|
||||
"#4429e6",
|
||||
"#fd6280",
|
||||
"#28dbe2",
|
||||
"#ffc404",
|
||||
"#b92325",
|
||||
"#1b9f17",
|
||||
"#fe8705",
|
||||
"#97b552",
|
||||
"#95706d",
|
||||
"#dc69aa",
|
||||
"#07a2a4",
|
||||
"#9a7fd1",
|
||||
"#588dd5",
|
||||
"#f5994e",
|
||||
"#333333"
|
||||
],
|
||||
"backgroundColor": "rgba(0,0,0,0)",
|
||||
"textStyle": {},
|
||||
"title": {
|
||||
"textStyle": {
|
||||
"color": "#333333"
|
||||
},
|
||||
"subtextStyle": {
|
||||
"color": "#999999"
|
||||
}
|
||||
},
|
||||
"line": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": "2"
|
||||
},
|
||||
"symbolSize": "5",
|
||||
"symbol": "circle",
|
||||
"smooth": true
|
||||
},
|
||||
"radar": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": "2"
|
||||
},
|
||||
"symbolSize": "5",
|
||||
"symbol": "circle",
|
||||
"smooth": true
|
||||
},
|
||||
"bar": {
|
||||
"itemStyle": {
|
||||
"barBorderWidth": "2",
|
||||
"barBorderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"pie": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"scatter": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"boxplot": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"parallel": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"sankey": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"funnel": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"gauge": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"candlestick": {
|
||||
"itemStyle": {
|
||||
"color": "#d87a80",
|
||||
"color0": "#2ec7c9",
|
||||
"borderColor": "#d87a80",
|
||||
"borderColor0": "#2ec7c9",
|
||||
"borderWidth": 1
|
||||
}
|
||||
},
|
||||
"graph": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": 1,
|
||||
"color": "#aaaaaa"
|
||||
},
|
||||
"symbolSize": "5",
|
||||
"symbol": "circle",
|
||||
"smooth": true,
|
||||
"color": [
|
||||
"#4429e6",
|
||||
"#fd6280",
|
||||
"#28dbe2",
|
||||
"#ffc404",
|
||||
"#b92325",
|
||||
"#1b9f17",
|
||||
"#fe8705",
|
||||
"#97b552",
|
||||
"#95706d",
|
||||
"#dc69aa",
|
||||
"#07a2a4",
|
||||
"#9a7fd1",
|
||||
"#588dd5",
|
||||
"#f5994e",
|
||||
"#333333"
|
||||
],
|
||||
"label": {
|
||||
"color": "#fefefe"
|
||||
}
|
||||
},
|
||||
"map": {
|
||||
"itemStyle": {
|
||||
"areaColor": "#dddddd",
|
||||
"borderColor": "#eeeeee",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"label": {
|
||||
"color": "#d87a80"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"areaColor": "rgba(254,153,78,1)",
|
||||
"borderColor": "#444",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"label": {
|
||||
"color": "rgb(100,0,0)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"geo": {
|
||||
"itemStyle": {
|
||||
"areaColor": "#dddddd",
|
||||
"borderColor": "#eeeeee",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"label": {
|
||||
"color": "#d87a80"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"areaColor": "rgba(254,153,78,1)",
|
||||
"borderColor": "#444",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"label": {
|
||||
"color": "rgb(100,0,0)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"categoryAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": false,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"rgba(250,250,250,0.3)",
|
||||
"rgba(200,200,200,0.3)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"valueAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": true,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"#ffffff",
|
||||
"rgba(0,0,0,0.02)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"logAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": true,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"#ffffff",
|
||||
"rgba(0,0,0,0.02)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"rgba(250,250,250,0.3)",
|
||||
"rgba(200,200,200,0.3)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"toolbox": {
|
||||
"iconStyle": {
|
||||
"borderColor": "#000000"
|
||||
},
|
||||
"emphasis": {
|
||||
"iconStyle": {
|
||||
"borderColor": "#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"color": "#333333"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"axisPointer": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.3)",
|
||||
"width": "1"
|
||||
},
|
||||
"crossStyle": {
|
||||
"color": "rgba(0,0,0,0.3)",
|
||||
"width": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
"lineStyle": {
|
||||
"color": "#008acd",
|
||||
"width": 1
|
||||
},
|
||||
"itemStyle": {
|
||||
"color": "#008acd",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"controlStyle": {
|
||||
"color": "#008acd",
|
||||
"borderColor": "#008acd",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"checkpointStyle": {
|
||||
"color": "#2ec7c9",
|
||||
"borderColor": "#2ec7c9"
|
||||
},
|
||||
"label": {
|
||||
"color": "#008acd"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"color": "#a9334c"
|
||||
},
|
||||
"controlStyle": {
|
||||
"color": "#008acd",
|
||||
"borderColor": "#008acd",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"label": {
|
||||
"color": "#008acd"
|
||||
}
|
||||
}
|
||||
},
|
||||
"visualMap": {
|
||||
"color": [
|
||||
"#ffffff",
|
||||
"#4429e6"
|
||||
]
|
||||
},
|
||||
"dataZoom": {
|
||||
"backgroundColor": "rgba(47,69,84,0)",
|
||||
"dataBackgroundColor": "#efefff",
|
||||
"fillerColor": "rgba(182,162,222,0.2)",
|
||||
"handleColor": "#008acd",
|
||||
"handleSize": "100%",
|
||||
"textStyle": {
|
||||
"color": "#333333"
|
||||
}
|
||||
},
|
||||
"markPoint": {
|
||||
"label": {
|
||||
"color": "#fefefe"
|
||||
},
|
||||
"emphasis": {
|
||||
"label": {
|
||||
"color": "#fefefe"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
@@ -0,0 +1,409 @@
|
||||
{
|
||||
"color": [
|
||||
"#4429e6",
|
||||
"#fd6280",
|
||||
"#28dbe2",
|
||||
"#ffc404",
|
||||
"#b92325",
|
||||
"#1b9f17",
|
||||
"#fe8705",
|
||||
"#97b552",
|
||||
"#95706d",
|
||||
"#dc69aa",
|
||||
"#07a2a4",
|
||||
"#9a7fd1",
|
||||
"#588dd5",
|
||||
"#f5994e",
|
||||
"#333333"
|
||||
],
|
||||
"backgroundColor": "rgba(0,0,0,0)",
|
||||
"textStyle": {},
|
||||
"title": {
|
||||
"textStyle": {
|
||||
"color": "#333333"
|
||||
},
|
||||
"subtextStyle": {
|
||||
"color": "#999999"
|
||||
}
|
||||
},
|
||||
"line": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": "2"
|
||||
},
|
||||
"symbolSize": "5",
|
||||
"symbol": "circle",
|
||||
"smooth": true
|
||||
},
|
||||
"radar": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": "2"
|
||||
},
|
||||
"symbolSize": "5",
|
||||
"symbol": "circle",
|
||||
"smooth": true
|
||||
},
|
||||
"bar": {
|
||||
"itemStyle": {
|
||||
"barBorderWidth": "2",
|
||||
"barBorderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"pie": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"scatter": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"boxplot": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"parallel": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"sankey": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"funnel": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"gauge": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"candlestick": {
|
||||
"itemStyle": {
|
||||
"color": "#d87a80",
|
||||
"color0": "#2ec7c9",
|
||||
"borderColor": "#d87a80",
|
||||
"borderColor0": "#2ec7c9",
|
||||
"borderWidth": 1
|
||||
}
|
||||
},
|
||||
"graph": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": 1,
|
||||
"color": "#aaaaaa"
|
||||
},
|
||||
"symbolSize": "5",
|
||||
"symbol": "circle",
|
||||
"smooth": true,
|
||||
"color": [
|
||||
"#4429e6",
|
||||
"#fd6280",
|
||||
"#28dbe2",
|
||||
"#ffc404",
|
||||
"#b92325",
|
||||
"#1b9f17",
|
||||
"#fe8705",
|
||||
"#97b552",
|
||||
"#95706d",
|
||||
"#dc69aa",
|
||||
"#07a2a4",
|
||||
"#9a7fd1",
|
||||
"#588dd5",
|
||||
"#f5994e",
|
||||
"#333333"
|
||||
],
|
||||
"label": {
|
||||
"color": "#fefefe"
|
||||
}
|
||||
},
|
||||
"map": {
|
||||
"itemStyle": {
|
||||
"areaColor": "#dddddd",
|
||||
"borderColor": "#eeeeee",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"label": {
|
||||
"color": "#d87a80"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"areaColor": "rgba(254,153,78,1)",
|
||||
"borderColor": "#444",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"label": {
|
||||
"color": "rgb(100,0,0)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"geo": {
|
||||
"itemStyle": {
|
||||
"areaColor": "#dddddd",
|
||||
"borderColor": "#eeeeee",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"label": {
|
||||
"color": "#d87a80"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"areaColor": "rgba(254,153,78,1)",
|
||||
"borderColor": "#444",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"label": {
|
||||
"color": "rgb(100,0,0)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"categoryAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": false,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"rgba(250,250,250,0.3)",
|
||||
"rgba(200,200,200,0.3)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"valueAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": true,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"#ffffff",
|
||||
"rgba(0,0,0,0.02)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"logAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": true,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"#ffffff",
|
||||
"rgba(0,0,0,0.02)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"rgba(250,250,250,0.3)",
|
||||
"rgba(200,200,200,0.3)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"toolbox": {
|
||||
"iconStyle": {
|
||||
"borderColor": "#000000"
|
||||
},
|
||||
"emphasis": {
|
||||
"iconStyle": {
|
||||
"borderColor": "#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"color": "#333333"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"axisPointer": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.3)",
|
||||
"width": "1"
|
||||
},
|
||||
"crossStyle": {
|
||||
"color": "rgba(0,0,0,0.3)",
|
||||
"width": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
"lineStyle": {
|
||||
"color": "#008acd",
|
||||
"width": 1
|
||||
},
|
||||
"itemStyle": {
|
||||
"color": "#008acd",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"controlStyle": {
|
||||
"color": "#008acd",
|
||||
"borderColor": "#008acd",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"checkpointStyle": {
|
||||
"color": "#2ec7c9",
|
||||
"borderColor": "#2ec7c9"
|
||||
},
|
||||
"label": {
|
||||
"color": "#008acd"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"color": "#a9334c"
|
||||
},
|
||||
"controlStyle": {
|
||||
"color": "#008acd",
|
||||
"borderColor": "#008acd",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"label": {
|
||||
"color": "#008acd"
|
||||
}
|
||||
}
|
||||
},
|
||||
"visualMap": {
|
||||
"color": [
|
||||
"#ffffff",
|
||||
"#4429e6"
|
||||
]
|
||||
},
|
||||
"dataZoom": {
|
||||
"backgroundColor": "rgba(47,69,84,0)",
|
||||
"dataBackgroundColor": "#efefff",
|
||||
"fillerColor": "rgba(182,162,222,0.2)",
|
||||
"handleColor": "#008acd",
|
||||
"handleSize": "100%",
|
||||
"textStyle": {
|
||||
"color": "#333333"
|
||||
}
|
||||
},
|
||||
"markPoint": {
|
||||
"label": {
|
||||
"color": "#fefefe"
|
||||
},
|
||||
"emphasis": {
|
||||
"label": {
|
||||
"color": "#fefefe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// 导入echarts核心模块
|
||||
import * as echarts from 'echarts/core'
|
||||
// 导入主题JSON
|
||||
import themeJson from './apipark-chart-palette.json'
|
||||
|
||||
// 全局注册主题
|
||||
export function registerApiparkTheme() {
|
||||
echarts.registerTheme('apipark', themeJson)
|
||||
}
|
||||
|
||||
// 导出主题名称,方便组件使用
|
||||
export const THEME_NAME = 'apipark'
|
||||
@@ -0,0 +1,11 @@
|
||||
// 导入主题配置
|
||||
import themeJson from './apipark-chart-palette.json'
|
||||
|
||||
// 导出主题配置
|
||||
export const apiparkTheme = themeJson
|
||||
|
||||
// 导出颜色列表,方便单独使用
|
||||
export const chartColors = themeJson.color
|
||||
|
||||
// 导出默认颜色
|
||||
export const defaultColor = chartColors[0]
|
||||
@@ -119,6 +119,11 @@ export const PERMISSION_DEFINITION = [
|
||||
anyOf: [{ backend: ['system.organization.role.manager_team_role'] }]
|
||||
}
|
||||
},
|
||||
'system.organization.auth.view': {
|
||||
granted: {
|
||||
anyOf: [{ backend: ['system.settings.login.manager', 'system.settings.login.view'] }]
|
||||
}
|
||||
},
|
||||
'system.api_market.service_classification.view': {
|
||||
granted: {
|
||||
anyOf: [{ backend: ['system.settings.general.view'] }]
|
||||
@@ -634,6 +639,15 @@ export const PERMISSION_DEFINITION = [
|
||||
]
|
||||
}
|
||||
},
|
||||
'team.consumer.mcp.view': {
|
||||
granted: {
|
||||
anyOf: [
|
||||
{
|
||||
backend: ['team.consumer.mcp.manager', 'team.consumer.mcp.view']
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
'team.application.authorization.add': {
|
||||
granted: {
|
||||
anyOf: [{ backend: ['system.workspace.application.manager_all', 'team.consumer.authorization.manager'] }]
|
||||
|
||||
@@ -22,8 +22,10 @@ export const BreadcrumbProvider = ({ children }: unknown) => {
|
||||
<BreadcrumbContext.Provider
|
||||
value={{
|
||||
setBreadcrumb: (newItems) => {
|
||||
newItems.slice(0, newItems.length - 1).forEach((item) => {
|
||||
item.title = <span className="cursor-pointer hover:text-theme">{item.title}</span>
|
||||
newItems.forEach((item) => {
|
||||
item.title = (
|
||||
<span className={`${item.onClick ? 'cursor-pointer hover:text-theme' : ''}`}>{item.title}</span>
|
||||
)
|
||||
})
|
||||
setBreadcrumb(newItems)
|
||||
},
|
||||
|
||||
@@ -87,8 +87,8 @@ const mockData = [
|
||||
},
|
||||
{
|
||||
name: 'API 市场',
|
||||
key: 'serviceHub',
|
||||
path: '/serviceHub',
|
||||
key: 'portal',
|
||||
path: '/portal',
|
||||
icon: 'ic:baseline-hub',
|
||||
access: 'system.api_portal.api_portal.view'
|
||||
},
|
||||
@@ -107,15 +107,15 @@ const mockData = [
|
||||
},
|
||||
{
|
||||
name: '服务',
|
||||
key: 'analyticsSubscriber',
|
||||
path: '/analytics/subscriber/list',
|
||||
key: 'analyticsService',
|
||||
path: '/analytics/service/list',
|
||||
icon: 'ic:baseline-blinds-closed',
|
||||
access: 'system.analysis.run_view.view'
|
||||
},
|
||||
{
|
||||
name: '消费者',
|
||||
key: 'analyticsProvider',
|
||||
path: '/analytics/provider/list',
|
||||
key: 'analyticsConsumer',
|
||||
path: '/analytics/consumer/list',
|
||||
icon: 'ic:baseline-apps',
|
||||
access: 'system.analysis.run_view.view'
|
||||
},
|
||||
@@ -207,8 +207,15 @@ const mockData = [
|
||||
name: '角色',
|
||||
key: 'role',
|
||||
path: '/role',
|
||||
icon: 'ic:baseline-verified-user',
|
||||
icon: 'ph:user-circle-gear-fill',
|
||||
access: 'system.organization.role.view'
|
||||
},
|
||||
{
|
||||
name: '鉴权',
|
||||
key: 'auth',
|
||||
path: '/auth',
|
||||
icon: 'ic:baseline-verified-user',
|
||||
access: 'system.organization.auth.view'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -253,7 +260,7 @@ const mockData = [
|
||||
access: 'system.settings.ssl_certificate.view'
|
||||
},
|
||||
{
|
||||
name: '日志',
|
||||
name: '日志输出',
|
||||
key: 'logsettings',
|
||||
path: '/logsettings',
|
||||
icon: 'ic:baseline-sticky-note-2',
|
||||
|
||||
@@ -60,6 +60,16 @@ const mockData = {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
driver: 'apipark.builtIn.component',
|
||||
name: 'auth',
|
||||
router: [
|
||||
{
|
||||
path: 'auth',
|
||||
type: 'normal'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
driver: 'apipark.builtIn.component',
|
||||
name: 'cluster',
|
||||
@@ -112,10 +122,10 @@ const mockData = {
|
||||
},
|
||||
{
|
||||
driver: 'apipark.builtIn.component',
|
||||
name: 'serviceHub',
|
||||
name: 'portal',
|
||||
router: [
|
||||
{
|
||||
path: 'serviceHub',
|
||||
path: 'portal',
|
||||
type: 'normal'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -54,6 +54,10 @@
|
||||
"上游列表": "K54e44357",
|
||||
"备注": "Kb8e8e6f5",
|
||||
"上线情况": "K7e52ffa3",
|
||||
"服务 ID": "K1e84ad04",
|
||||
"服务尚未发布": "Ke1e649cb",
|
||||
"跳转至详情页": "K2e683a7d",
|
||||
"暂无服务描述": "Ka4b45550",
|
||||
"申请原因": "K1ab0ae5b",
|
||||
"审核意见": "K53c00c3c",
|
||||
"暂无(0)权限,请联系管理员分配。": "Kfd50704d",
|
||||
@@ -114,6 +118,7 @@
|
||||
"无需审核:允许任何消费者调用该服务": "K1fc2cc28",
|
||||
"人工审核:仅允许通过人工审核的消费者调用该服务": "K8dabb98e",
|
||||
"开启:AI Agent 等产品能够通过 MCP 方式调用服务": "Ke959f135",
|
||||
"总览": "Kaf9e8011",
|
||||
"永久": "Kbfe02d7f",
|
||||
"否": "K1e9c479e",
|
||||
"是": "Kaddfcb6b",
|
||||
@@ -241,6 +246,9 @@
|
||||
"调用地址": "K2f5fdf5e",
|
||||
"消费者 IP": "K1bc5e0a3",
|
||||
"鉴权名称": "K6f39ea21",
|
||||
"日志输出": "K3c722abd",
|
||||
"响应时间": "K1be06929",
|
||||
"时间戳": "K5e51f5d",
|
||||
"暂无操作权限,请联系管理员分配。": "K23fda291",
|
||||
"微信小程序": "K4618cb0a",
|
||||
"获取文件,需填路径": "Ka854f511",
|
||||
@@ -345,11 +353,10 @@
|
||||
"重置": "K50d471b2",
|
||||
"查询": "Kee8ae330",
|
||||
"请输入 APIURL 搜索": "Kf8187c33",
|
||||
"服务": "Kb58e0c3f",
|
||||
"说明文档": "K6cd677b",
|
||||
"最近一次更新者": "K617f34f1",
|
||||
"最近一次更新时间": "K6ebca204",
|
||||
"保存": "Kabfe9512",
|
||||
"服务": "Kb58e0c3f",
|
||||
"API 路由": "K51d1eb5d",
|
||||
"API 文档": "Ka2b6d281",
|
||||
"使用说明": "Kdefa9caa",
|
||||
@@ -361,14 +368,10 @@
|
||||
"管理": "K5974bf24",
|
||||
"调用拓扑图": "K3fa5c4c3",
|
||||
"设置": "Kb5c7b82d",
|
||||
"服务 ID": "K1e84ad04",
|
||||
"新增订阅方": "K39ab0358",
|
||||
"手动添加": "K18307d56",
|
||||
"订阅申请": "K705fe9f5",
|
||||
"订阅方": "K3a67ea90",
|
||||
"API": "K3ba29a85",
|
||||
"编辑 API": "Ke93388fd",
|
||||
"添加 API": "K84aabfd4",
|
||||
"AI 路由设置": "Kefa2a4cf",
|
||||
"路由名称": "K66060758",
|
||||
"请求路径": "K5582ac8",
|
||||
@@ -379,7 +382,6 @@
|
||||
"拦截接口": "Kee4139c2",
|
||||
"开启拦截后,网关会拦截所有该路径的请求。": "K3e38ea",
|
||||
"模型配置": "K8a35059b",
|
||||
"路由": "Kf9dcef3a",
|
||||
"添加路由": "K6134bbe8",
|
||||
"输入 URL 查找路由": "Kf85b83a0",
|
||||
"线上模型": "K84b2cf2d",
|
||||
@@ -459,6 +461,17 @@
|
||||
"待审核": "K35612f29",
|
||||
"已审核": "K47eaafde",
|
||||
"发布申请": "K56b4254f",
|
||||
"鉴权": "Kb35e6a18",
|
||||
"系统用户账号登录授权配置": "K679bd7e4",
|
||||
"授权类型": "K9e7bb257",
|
||||
"请选择授权类型": "Kc499fc1d",
|
||||
"APP ID": "Kee9f8f26",
|
||||
"请输入APP ID": "K9e4c19bb",
|
||||
"APP ID 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上": "K45c99f97",
|
||||
"APP Secret": "K90f7c3b4",
|
||||
"请输入APP Secret": "Kdc53d96f",
|
||||
"APP Secret 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上": "K56e77c4",
|
||||
"启用授权": "K50693bd8",
|
||||
"API 调用地址": "Kea2f9279",
|
||||
"API base URL 一般设置为API 网关的外部网络访问地址,或者是API网关绑定的域名。": "K7fc496a1",
|
||||
"OpenAPI & MCP 调用地址": "Ka7ca8fde",
|
||||
@@ -551,14 +564,21 @@
|
||||
"请输入密码": "K25c895d5",
|
||||
"密码": "K551b0348",
|
||||
"登录": "Kd2c1a316",
|
||||
"飞书授权登录": "K682b11cb",
|
||||
"访客模式": "K192b3e38",
|
||||
"您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。": "K91aa4801",
|
||||
"Version (0)-(1)": "K480045ce",
|
||||
"日志配置": "Kadee8e49",
|
||||
"日志输出设置": "K74a5fbc0",
|
||||
"提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。": "K2724314b",
|
||||
"日志配置": "Kadee8e49",
|
||||
"MCP 配置": "K6e9c928f",
|
||||
"Open API 文档": "Kb6d0eb39",
|
||||
"AI 代理集成": "Ke6908f16",
|
||||
"请先订阅该服务": "K71ed51fa",
|
||||
"申请": "K4aa9ed2c",
|
||||
"未配置 API Key": "Kf7b54a1",
|
||||
"配置": "K2a1422d2",
|
||||
"选择 API Key": "K1bec8cbe",
|
||||
"新增 API Key": "Kb0e0aeda",
|
||||
"API 密钥可用于调用系统级 Open API 和 MCP。": "K9d81999c",
|
||||
"MCP 服务": "Kf106bc62",
|
||||
@@ -619,7 +639,7 @@
|
||||
"数据源": "K8fa58214",
|
||||
"设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。": "Kdbafd6f9",
|
||||
"统计图表": "K1358acf",
|
||||
"数据日志": "K17dc3a62",
|
||||
"请求日志": "Kc8bf447",
|
||||
"地址(IP:端口)": "K62dabdf6",
|
||||
"组织(Organization)": "K2db12335",
|
||||
"添加策略": "K34d0d409",
|
||||
@@ -627,8 +647,6 @@
|
||||
"处理日志": "Ke429194e",
|
||||
"脱敏前": "K8c34c02f",
|
||||
"脱敏后": "K8e3d388d",
|
||||
"编辑服务策略": "Kf06f6737",
|
||||
"添加服务策略": "K205971e1",
|
||||
"编辑策略": "Kc82b8374",
|
||||
"策略类型": "K4b34a5e5",
|
||||
"匹配条件": "K57f0fee8",
|
||||
@@ -660,6 +678,28 @@
|
||||
"系统级别角色": "K138facd3",
|
||||
"添加角色": "K6eac768d",
|
||||
"团队级别角色": "Kb9c2cf02",
|
||||
"API / Tools": "K9d526cac",
|
||||
"消费者": "K7acfcfad",
|
||||
"HTTP 状态": "Kc68ba0f4",
|
||||
"IP": "Kb09b747",
|
||||
"通过系统级别的 API Key 来调用": "K2eacb44f",
|
||||
"日志详情": "K764bca7c",
|
||||
"暂无数据": "Kf8525cf2",
|
||||
"输入 Token": "K33bc1ad1",
|
||||
"输出 Token": "Ke00ff18b",
|
||||
"订阅数量": "Ke04bc00d",
|
||||
"已开启": "K1b97ae0a",
|
||||
"开启 MCP": "K19ec733b",
|
||||
"API 使用排名": "Kbee2340",
|
||||
"消费者使用排名": "Kf6af1f40",
|
||||
"请求次数": "K9d3f2d9d",
|
||||
"网络流量": "Ke2241377",
|
||||
"平均响应时间": "K7c8d5c23",
|
||||
"平均每消费者的请求次数": "K6c267c7b",
|
||||
"平均每消费者的网络流量": "K133d4291",
|
||||
"Token 消耗": "K37c5f1d0",
|
||||
"平均 Token 消耗": "K10a8bee3",
|
||||
"平均每消费者的 Token 消耗": "Kb98264d4",
|
||||
"单位:ms,最小值:1": "K2a16c93b",
|
||||
"API 路由设置": "Ka945cfb1",
|
||||
"API 基础信息": "K2e050340",
|
||||
@@ -742,7 +782,6 @@
|
||||
"退出全屏": "Kaf70c3b",
|
||||
"(0)调用详情": "Kd22841a4",
|
||||
"消费者调用统计": "K61cca533",
|
||||
"消费者": "K7acfcfad",
|
||||
"请选择消费者": "Kdfff59d4",
|
||||
"调用趋势": "K8c7f2d2e",
|
||||
"(0)-(1)调用趋势": "K657c3452",
|
||||
@@ -783,14 +822,13 @@
|
||||
"配置集群信息": "Ke5ed9810",
|
||||
"监控设置": "K1a132228",
|
||||
"配置监控信息": "K6af08c3c",
|
||||
"监控总览": "K4a1a14",
|
||||
"服务被调用统计": "K69741ea7",
|
||||
"API 调用统计": "K9c8d9933",
|
||||
"加载数据失败,请重试": "K6c2d93b6",
|
||||
"亿": "K145e4941",
|
||||
"万": "Ke6a935d",
|
||||
"搜索分类或标签": "Kd59290a2",
|
||||
"暂无API数据": "K6b75bdbc",
|
||||
"搜索或选择消费者": "Kb684c806",
|
||||
"该消费者已订阅": "K5611e01e",
|
||||
"申请理由": "K4b15d6f5",
|
||||
"支持把当前服务对接主流的 AI Agent平台,实现在 Agent 平台上快速、安全和合规地使用企业开放的 API 能力。": "K2ec0fa56",
|
||||
"可按以下步骤进行对接:": "K35f23b64",
|
||||
@@ -850,8 +888,7 @@
|
||||
"版本": "K81634069",
|
||||
"更新时间": "Keefda53d",
|
||||
"介绍": "K59cdbec3",
|
||||
"暂无服务描述": "Ka4b45550",
|
||||
"申请": "K4aa9ed2c",
|
||||
"API": "K3ba29a85",
|
||||
"无标签": "K96a2f1c8",
|
||||
"分类": "Kb32f0afe",
|
||||
"服务市场": "K370a3eb2",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"Kb58e0c3f": "Service",
|
||||
"Kc9e489f5": "Team",
|
||||
"K61c89f5f": "API Portal",
|
||||
"K16d71239": "Analysis",
|
||||
"K16d71239": "Analytics",
|
||||
"K714c192d": "Call Statistics",
|
||||
"Kd57dfe97": "Topology",
|
||||
"K3fe97dcc": "System Settings",
|
||||
@@ -186,7 +186,7 @@
|
||||
"K617f34f1": "Updated By",
|
||||
"K6ebca204": "Update Time",
|
||||
"Kabfe9512": "Save",
|
||||
"K51d1eb5d": "API",
|
||||
"K51d1eb5d": "API Routes",
|
||||
"Ka2b6d281": "API Docs",
|
||||
"Kdefa9caa": "Usage Instructions",
|
||||
"K36856e71": "Publish",
|
||||
@@ -210,7 +210,7 @@
|
||||
"K469e475a": "Max Retry Times",
|
||||
"K8a35059b": "Model Settings",
|
||||
"Kf9dcef3a": "API",
|
||||
"K6134bbe8": "Add API",
|
||||
"K6134bbe8": "Add API Route",
|
||||
"Kf85b83a0": "Enter URL to Search",
|
||||
"Kcf9f90b8": "Model Provider",
|
||||
"Kfede1c7c": "Model",
|
||||
@@ -927,5 +927,64 @@
|
||||
"K71ed51fa": "Please subscribe to the service first",
|
||||
"K1bec8cbe": "Select API Key",
|
||||
"K5611e01e": "This consumer is already subscribed",
|
||||
"Kaf9e8011": "Overview"
|
||||
"Kaf9e8011": "Overview",
|
||||
"Ke1e649cb": "Service not released",
|
||||
"K2e683a7d": "Open in Portal",
|
||||
"Ke04bc00d": "Subscribers",
|
||||
"K1b97ae0a": "Enabled",
|
||||
"K19ec733b": "Enable MCP",
|
||||
"Kbee2340": "Top API",
|
||||
"Kf6af1f40": "Top Consumer",
|
||||
"K318a7519": "Requests",
|
||||
"K9ef68e3f": "Token",
|
||||
"Kfb14ccb0": "Models",
|
||||
"K10a8bee3": "Avg Token per Second",
|
||||
"K2727b76b": "Avg Requests per Subscriber",
|
||||
"K4c7a6704": "Avg Token per Subscriber",
|
||||
"K53eb7414": "Traffic",
|
||||
"K7c8d5c23": "Avg Response Time",
|
||||
"Kf9eb702": "QRS",
|
||||
"K7f0aa740": "Avg Traffic per Subscriber",
|
||||
"K9d526cac": "API / Tools",
|
||||
"Kc68ba0f4": "HTTP Status",
|
||||
"Kb09b747": "IP",
|
||||
"K2eacb44f": "Request the API using a system-level API Key",
|
||||
"K764bca7c": "Log Detail",
|
||||
"K6c016898": "Avg Token per Second",
|
||||
"K652843b0": "Avg Requests per Subscriber",
|
||||
"Kdbf831a0": "Avg Token per Subscriber",
|
||||
"K8158a6e4": "Avg Traffic per Subscriber",
|
||||
"K6b882d4a": "Avg Token per Subscriber",
|
||||
"K6c2d93b6": "Failed to load data, please try again",
|
||||
"Kf5eeb9c5": "Avg Token per Subscriber",
|
||||
"K1639a17a": "API Routes Docs",
|
||||
"K33bc1ad1": "Input Token",
|
||||
"Ke00ff18b": "Output Token",
|
||||
"K81140e5b": "Total Token",
|
||||
"K3c722abd": "Log Output",
|
||||
"K74a5fbc0": "Log Output Settings",
|
||||
"Kc8bf447": "Request Log",
|
||||
"Kf8525cf2": "No Data",
|
||||
"K1be06929": "Response Time",
|
||||
"K5e51f5d": "Timestamp",
|
||||
"K9d3f2d9d": "Requests",
|
||||
"Ke2241377": "Traffic",
|
||||
"K6c267c7b": "Avg Requests per Subscriber",
|
||||
"K133d4291": "Avg Traffic per Subscriber",
|
||||
"K37c5f1d0": "Token",
|
||||
"Kb98264d4": "Avg Token per Subscriber",
|
||||
"Kb35e6a18": "Auth",
|
||||
"K679bd7e4": "System User Account Login Authorization Settings",
|
||||
"K9e7bb257": "Authorization Type",
|
||||
"Kc499fc1d": "Please select an authorization type",
|
||||
"Kee9f8f26": "APP ID",
|
||||
"K9e4c19bb": "Please enter APP ID",
|
||||
"K90f7c3b4": "APP Secret",
|
||||
"Kdc53d96f": "Please enter APP Secret",
|
||||
"K50693bd8": "Enable Authorization",
|
||||
"K682b11cb": "Feishu Authorization Login",
|
||||
"K45c99f97": "The APP ID parameter can be found on the App Credentials and Basic Information page in the Feishu Developer Console",
|
||||
"K56e77c4": "The APP Secret parameter can be found on the App Credentials and Basic Information page in the Feishu Developer Console",
|
||||
"Kf7b54a1": "API Key not configured",
|
||||
"K2a1422d2": "Configure"
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
"K617f34f1": "更新者",
|
||||
"K6ebca204": "更新日時",
|
||||
"Kabfe9512": "保存",
|
||||
"K51d1eb5d": "API",
|
||||
"K51d1eb5d": "APIルート",
|
||||
"Ka2b6d281": "API ドキュメント",
|
||||
"Kdefa9caa": "説明ドキュメント",
|
||||
"K36856e71": "公開",
|
||||
@@ -213,7 +213,7 @@
|
||||
"K469e475a": "リトライ回数",
|
||||
"K8a35059b": "モデル設定",
|
||||
"Kf9dcef3a": "API",
|
||||
"K6134bbe8": "API を追加",
|
||||
"K6134bbe8": "APIルートを追加する",
|
||||
"Kf85b83a0": "URL を入力して検索",
|
||||
"Kcf9f90b8": "モデルプロバイダー",
|
||||
"Kfede1c7c": "モデル",
|
||||
@@ -949,5 +949,64 @@
|
||||
"K71ed51fa": "このサービスに先にサブスクリプションしてください",
|
||||
"K1bec8cbe": "APIキーを選択してください",
|
||||
"K5611e01e": "この消費者はすでに購読しています",
|
||||
"Kaf9e8011": "概要"
|
||||
"Kaf9e8011": "概要",
|
||||
"Ke1e649cb": "サービスはまだ公開されていません",
|
||||
"K2e683a7d": "詳細ページへ移動",
|
||||
"Ke04bc00d": "サブスクリプション数",
|
||||
"K1b97ae0a": "有効",
|
||||
"K19ec733b": "MCP を有効にする",
|
||||
"Kbee2340": "API 使用ランキング",
|
||||
"Kf6af1f40": "コンシューマー使用ランキング",
|
||||
"K318a7519": "リクエスト数",
|
||||
"K9ef68e3f": "トークン",
|
||||
"Kfb14ccb0": "モデル使用量",
|
||||
"K10a8bee3": "平均トークン消費量",
|
||||
"K2727b76b": "ユーザーあたり平均リクエスト数",
|
||||
"K4c7a6704": "ユーザーあたり平均トークン消費量",
|
||||
"K53eb7414": "トラフィック",
|
||||
"K7c8d5c23": "平均応答時間",
|
||||
"Kf9eb702": "毎秒リクエスト数",
|
||||
"K7f0aa740": "ユーザーあたり平均トラフィック",
|
||||
"K9d526cac": "API / ツール",
|
||||
"Kc68ba0f4": "HTTP ステータス",
|
||||
"Kb09b747": "IP",
|
||||
"K2eacb44f": "システムレベルの API Key で呼び出し",
|
||||
"K764bca7c": "ログ詳細",
|
||||
"K6c016898": "平均トークン/s 統計",
|
||||
"K652843b0": "平均リクエスト数",
|
||||
"Kdbf831a0": "平均トークン/加入者 統計",
|
||||
"K8158a6e4": "平均トラフィック",
|
||||
"K6b882d4a": "平均トークン/加入者",
|
||||
"K6c2d93b6": "データの読み込みに失敗しました。もう一度お試しください",
|
||||
"Kf5eeb9c5": "平均トークン/加入者 統計",
|
||||
"K1639a17a": "APIルートのドキュメント",
|
||||
"K33bc1ad1": "入力トークン",
|
||||
"Ke00ff18b": "出力トークン",
|
||||
"K81140e5b": "合計トークン",
|
||||
"K3c722abd": "ログ出力",
|
||||
"K74a5fbc0": "ログ出力設定",
|
||||
"Kc8bf447": "リクエストログ",
|
||||
"Kf8525cf2": "データがありません",
|
||||
"K1be06929": "応答時間",
|
||||
"K5e51f5d": "タイムスタンプ",
|
||||
"K9d3f2d9d": "リクエスト数",
|
||||
"Ke2241377": "ネットワークトラフィック",
|
||||
"K6c267c7b": "消費者あたりの平均リクエスト数",
|
||||
"K133d4291": "消費者あたりの平均ネットワークトラフィック",
|
||||
"K37c5f1d0": "トークン消費量",
|
||||
"Kb98264d4": "消費者あたりの平均トークン消費量",
|
||||
"Kb35e6a18": "認証",
|
||||
"K679bd7e4": "システムユーザーアカウントログイン認可設定",
|
||||
"K9e7bb257": "認可タイプ",
|
||||
"Kc499fc1d": "認可タイプを選択してください",
|
||||
"Kee9f8f26": "APP ID",
|
||||
"K9e4c19bb": "APP ID を入力してください",
|
||||
"K90f7c3b4": "APP Secret",
|
||||
"Kdc53d96f": "APP Secret を入力してください",
|
||||
"K50693bd8": "認可を有効化",
|
||||
"K682b11cb": "Feishu 認証ログイン",
|
||||
"K45c99f97": "APP ID パラメータは Feishu 開発者コンソールのアプリ認証情報と基本情報ページにあります",
|
||||
"K56e77c4": "APP Secret パラメータは Feishu 開発者コンソールのアプリ認証情報と基本情報ページにあります",
|
||||
"Kf7b54a1": "API Key が設定されていません",
|
||||
"K2a1422d2": "設定"
|
||||
}
|
||||
|
||||
@@ -1,69 +1 @@
|
||||
{
|
||||
"K630c9e6d": "APIPark",
|
||||
"Ka3e9f580": "发布名称",
|
||||
"Kb2480682": "策略列表",
|
||||
"K76036e25": "HTTP 请求头",
|
||||
"K44607e3f": "全等匹配",
|
||||
"Kc287500a": "前缀匹配",
|
||||
"Kfc0b1147": "后缀匹配",
|
||||
"Ka4a92043": "子串匹配",
|
||||
"K30b2e44f": "非等匹配",
|
||||
"Kb1587991": "空值匹配",
|
||||
"K1e97dbd8": "存在匹配",
|
||||
"Kc8ee3e62": "不存在匹配",
|
||||
"K87c5a801": "区分大小写的正则匹配",
|
||||
"K95f062f1": "不区分大小写的正则匹配",
|
||||
"Kfbd230a5": "任意匹配",
|
||||
"Kd85208a3": "驳回",
|
||||
"Kad6aa439": "已订阅",
|
||||
"K9a68443b": "取消申请",
|
||||
"Kaeba0229": "透传客户端请求 Host",
|
||||
"K6d7e2fd0": "使用上游服务 Host",
|
||||
"K31332633": "重写 Host",
|
||||
"K2c2bc64f": "动态服务发现",
|
||||
"K78b1ca25": "地址",
|
||||
"K1644b775": "新增",
|
||||
"Kec91f0db": "申请方消费者",
|
||||
"K118d8d74": "数据格式",
|
||||
"Kfe7c7d2d": "关键字",
|
||||
"K2f57a694": "正则表达式",
|
||||
"K8953e0a6": "手机号",
|
||||
"K6f86a038": "身份证号",
|
||||
"K7954e7c8": "银行卡号",
|
||||
"K320fdb17": "金额",
|
||||
"K7867acda": "日期",
|
||||
"K7d327ae8": "局部显示",
|
||||
"Kfbf38e3c": "局部遮蔽",
|
||||
"Kd8c1fbb0": "截取",
|
||||
"K89829921": "替换",
|
||||
"K480a7165": "乱序",
|
||||
"Kea0d69df": "随机字符串",
|
||||
"Ke7c84d1d": "自定义字符串",
|
||||
"K49731763": "请输入IP地址或CIDR范围,每条以换行分割",
|
||||
"K3a34d49b": "待更新",
|
||||
"Kd2850420": "待删除",
|
||||
"K83237c89": "输入的IP或CIDR不符合格式",
|
||||
"K5ae2c87a": "请正确输入路径,如/usr/*或*/usr/*",
|
||||
"K67f4e9bb": "与外部平台集成时,获取 API 市场中文档信息的域名",
|
||||
"Kc82b8374": "编辑策略",
|
||||
"K4b34a5e5": "策略类型",
|
||||
"K57f0fee8": "匹配条件",
|
||||
"K10650c58": "数据脱敏规则",
|
||||
"K1b34a9ab": "配置脱敏规则",
|
||||
"K26d22405": "匹配值",
|
||||
"K1546e1fe": "脱敏类型",
|
||||
"K9b9b0629": "起始位置",
|
||||
"K52c84fe1": "长度",
|
||||
"Kde84409c": "替换类型",
|
||||
"K338653b4": "替换值",
|
||||
"Kbaeed3b7": "JSON Path",
|
||||
"K4cd91d61": "脱敏规则",
|
||||
"K8dcad979": "自定义字符串; 值:",
|
||||
"K82e3f7b7": "起始位置:(0)位;长度:(1)位",
|
||||
"K49dfc123": "已选择(0)项(1)数据",
|
||||
"K8457ea34": "所有(0)",
|
||||
"K7ca9a795": "属性名称",
|
||||
"Kc4391744": "属性值",
|
||||
"K678e13fc": "配置(0)",
|
||||
"Kf5fd27ed": "输入名称查找用户"
|
||||
}
|
||||
{}
|
||||
@@ -189,7 +189,7 @@
|
||||
"K617f34f1": "更新者",
|
||||
"K6ebca204": "更新时间",
|
||||
"Kabfe9512": "保存",
|
||||
"K51d1eb5d": "API",
|
||||
"K51d1eb5d": "API 路由",
|
||||
"Ka2b6d281": "API 文档",
|
||||
"Kdefa9caa": "说明文档",
|
||||
"K36856e71": "发布",
|
||||
@@ -213,7 +213,7 @@
|
||||
"K469e475a": "最大重试次数",
|
||||
"K8a35059b": "模型设置",
|
||||
"Kf9dcef3a": "API",
|
||||
"K6134bbe8": "添加 API",
|
||||
"K6134bbe8": "添加 API 路由",
|
||||
"Kf85b83a0": "输入 URL 查找",
|
||||
"Kcf9f90b8": "模型供应商",
|
||||
"Kfede1c7c": "模型",
|
||||
@@ -880,5 +880,62 @@
|
||||
"K71ed51fa": "请先订阅该服务",
|
||||
"K1bec8cbe": "选择 API Key",
|
||||
"K5611e01e": "该消费者已订阅",
|
||||
"Kaf9e8011": "总览"
|
||||
"Kaf9e8011": "总览",
|
||||
"Ke1e649cb": "服务尚未发布",
|
||||
"K2e683a7d": "跳转至详情页",
|
||||
"Ke04bc00d": "订阅方数量",
|
||||
"K1b97ae0a": "已开启",
|
||||
"K19ec733b": "开启 MCP",
|
||||
"Kbee2340": "API 使用排名",
|
||||
"Kf6af1f40": "消费者使用排名",
|
||||
"K318a7519": "请求数",
|
||||
"K9ef68e3f": "Token",
|
||||
"Kfb14ccb0": "模型使用量",
|
||||
"K10a8bee3": "平均 Token 消耗",
|
||||
"K2727b76b": "人均请求数",
|
||||
"K4c7a6704": "人均 Token 消耗",
|
||||
"K53eb7414": "流量",
|
||||
"K7c8d5c23": "平均响应时间",
|
||||
"Kf9eb702": "每秒请求数量",
|
||||
"K7f0aa740": "人均流量",
|
||||
"K9d526cac": "API / Tools",
|
||||
"Kc68ba0f4": "HTTP 状态",
|
||||
"Kb09b747": "IP",
|
||||
"K2eacb44f": "通过系统级别的 API Key 来调用",
|
||||
"K764bca7c": "日志详情",
|
||||
"K6c016898": "平均 Token/s 统计",
|
||||
"K652843b0": "平均请求数",
|
||||
"K8158a6e4": "平均流量",
|
||||
"K6c2d93b6": "加载数据失败,请重试",
|
||||
"Kf5eeb9c5": "平均 Token/订阅者统计",
|
||||
"K1639a17a": "API 路由文档",
|
||||
"K33bc1ad1": "输入 Token",
|
||||
"Ke00ff18b": "输出 Token",
|
||||
"K81140e5b": "总 Token",
|
||||
"K3c722abd": "日志输出",
|
||||
"K74a5fbc0": "日志输出设置",
|
||||
"Kc8bf447": "请求日志",
|
||||
"Kf8525cf2": "暂无数据",
|
||||
"K1be06929": "响应时间",
|
||||
"K5e51f5d": "时间",
|
||||
"K9d3f2d9d": "请求次数",
|
||||
"Ke2241377": "网络流量",
|
||||
"K6c267c7b": "平均每消费者的请求次数",
|
||||
"K133d4291": "平均每消费者的网络流量",
|
||||
"K37c5f1d0": "Token 消耗",
|
||||
"Kb98264d4": "平均每消费者的 Token 消耗",
|
||||
"Kb35e6a18": "鉴权",
|
||||
"K679bd7e4": "系统用户账号登录授权配置",
|
||||
"K9e7bb257": "授权类型",
|
||||
"Kc499fc1d": "请选择授权类型",
|
||||
"Kee9f8f26": "APP ID",
|
||||
"K9e4c19bb": "请输入APP ID",
|
||||
"K90f7c3b4": "APP Secret",
|
||||
"Kdc53d96f": "请输入APP Secret",
|
||||
"K50693bd8": "启用授权",
|
||||
"K682b11cb": "飞书授权登录",
|
||||
"K45c99f97": "APP ID 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上",
|
||||
"K56e77c4": "APP Secret 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上",
|
||||
"Kf7b54a1": "未配置 API Key",
|
||||
"K2a1422d2": "配置"
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
"K617f34f1": "更新者",
|
||||
"K6ebca204": "更新時間",
|
||||
"Kabfe9512": "保存",
|
||||
"K51d1eb5d": "API",
|
||||
"K51d1eb5d": "API 路由",
|
||||
"Ka2b6d281": "API 文檔",
|
||||
"Kdefa9caa": "說明文檔",
|
||||
"K36856e71": "發布",
|
||||
@@ -213,7 +213,7 @@
|
||||
"K469e475a": "最大重試次數",
|
||||
"K8a35059b": "模型設置",
|
||||
"Kf9dcef3a": "API",
|
||||
"K6134bbe8": "添加 API",
|
||||
"K6134bbe8": "添加 API 路由",
|
||||
"Kf85b83a0": "輸入 URL 查找",
|
||||
"Kcf9f90b8": "模型供應商",
|
||||
"Kfede1c7c": "模型",
|
||||
@@ -949,5 +949,64 @@
|
||||
"K71ed51fa": "請先訂閱該服務",
|
||||
"K1bec8cbe": "選擇 API Key",
|
||||
"K5611e01e": "該消費者已訂閱",
|
||||
"Kaf9e8011": "總覽"
|
||||
"Kaf9e8011": "總覽",
|
||||
"Ke1e649cb": "服務尚未發布",
|
||||
"K2e683a7d": "跳轉至詳情頁",
|
||||
"Ke04bc00d": "訂閱數量",
|
||||
"K1b97ae0a": "已開啟",
|
||||
"K19ec733b": "開啟 MCP",
|
||||
"Kbee2340": "API 使用排名",
|
||||
"Kf6af1f40": "消費者使用排名",
|
||||
"K318a7519": "請求數",
|
||||
"K9ef68e3f": "Token",
|
||||
"Kfb14ccb0": "模型使用量",
|
||||
"K10a8bee3": "平均 Token 消耗",
|
||||
"K2727b76b": "人均請求數",
|
||||
"K4c7a6704": "人均 Token 消耗",
|
||||
"K53eb7414": "流量",
|
||||
"K7c8d5c23": "平均回應時間",
|
||||
"Kf9eb702": "每秒請求數量",
|
||||
"K7f0aa740": "人均流量",
|
||||
"K9d526cac": "API / 工具",
|
||||
"Kc68ba0f4": "HTTP 狀態",
|
||||
"Kb09b747": "IP",
|
||||
"K2eacb44f": "透過系統級 API Key 調用",
|
||||
"K764bca7c": "日誌詳情",
|
||||
"K6c016898": "平均 Token/s 統計",
|
||||
"K652843b0": "平均請求數",
|
||||
"Kdbf831a0": "每位訂閱者平均 Token 統計",
|
||||
"K8158a6e4": "平均流量",
|
||||
"K6b882d4a": "每位訂閱者平均 Token",
|
||||
"K6c2d93b6": "載入資料失敗,請重試",
|
||||
"Kf5eeb9c5": "每位訂閱者平均 Token 統計",
|
||||
"K1639a17a": "API 路由文件",
|
||||
"K33bc1ad1": "輸入 Token",
|
||||
"Ke00ff18b": "輸出 Token",
|
||||
"K81140e5b": "總計 Token",
|
||||
"K3c722abd": "日誌輸出",
|
||||
"K74a5fbc0": "日誌輸出設定",
|
||||
"Kc8bf447": "請求日誌",
|
||||
"Kf8525cf2": "暫無資料",
|
||||
"K1be06929": "回應時間",
|
||||
"K5e51f5d": "時間",
|
||||
"K9d3f2d9d": "請求次數",
|
||||
"Ke2241377": "網路流量",
|
||||
"K6c267c7b": "平均每位使用者的請求次數",
|
||||
"K133d4291": "平均每位使用者的網路流量",
|
||||
"K37c5f1d0": "Token 消耗",
|
||||
"Kb98264d4": "平均每位使用者的 Token 消耗",
|
||||
"Kb35e6a18": "鑑權",
|
||||
"K679bd7e4": "系統用戶帳號登入授權配置",
|
||||
"K9e7bb257": "授權類型",
|
||||
"Kc499fc1d": "請選擇授權類型",
|
||||
"Kee9f8f26": "APP ID",
|
||||
"K9e4c19bb": "請輸入 APP ID",
|
||||
"K90f7c3b4": "APP Secret",
|
||||
"Kdc53d96f": "請輸入 APP Secret",
|
||||
"K50693bd8": "啟用授權",
|
||||
"K682b11cb": "飛書授權登入",
|
||||
"K45c99f97": "APP ID 參數位於飛書開發人員控制台中的應用程式憑證與基礎資訊頁面",
|
||||
"K56e77c4": "APP Secret 參數位於飛書開發人員控制台中的應用程式憑證與基礎資訊頁面",
|
||||
"Kf7b54a1": "未配置 API Key",
|
||||
"K2a1422d2": "配置"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { PluginEventHubProvider } from '@common/contexts/PluginEventHubContext'
|
||||
import { PluginSlotHubProvider } from '@common/contexts/PluginSlotHubContext'
|
||||
import useInitializeMonaco from '@common/hooks/useInitializeMonaco'
|
||||
import { $t } from '@common/locales'
|
||||
import { registerApiparkTheme } from '@common/const/charts/initChartTheme'
|
||||
import RenderRoutes from '@core/components/aoplatform/RenderRoutes'
|
||||
import { App as AppAntd, ConfigProvider } from 'antd'
|
||||
import { useMemo } from 'react'
|
||||
@@ -130,6 +131,9 @@ const antdComponentThemeToken = {
|
||||
}
|
||||
}
|
||||
|
||||
// 注册 ECharts 主题
|
||||
registerApiparkTheme()
|
||||
|
||||
function App() {
|
||||
const { locale } = useLocaleContext()
|
||||
useInitializeMonaco()
|
||||
|
||||
@@ -6,17 +6,18 @@ import { AiServiceRouterTableListItem, VariableItems } from './type'
|
||||
import { PageProColumns } from '@common/components/aoplatform/PageList'
|
||||
|
||||
export const AI_SERVICE_ROUTER_TABLE_COLUMNS: PageProColumns<AiServiceRouterTableListItem>[] = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'URL',
|
||||
dataIndex: 'requestPath',
|
||||
ellipsis: true,
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '模型',
|
||||
dataIndex: ['model', 'name'],
|
||||
|
||||
@@ -15,7 +15,7 @@ export type AiServiceConfigFieldType = {
|
||||
logoFile?:UploadFile;
|
||||
tags?:Array<string>;
|
||||
description?: string;
|
||||
team?:string;
|
||||
team?:EntityItem;
|
||||
master?:string;
|
||||
serviceType?:'public'|'inner';
|
||||
catalogue?:string | string[];
|
||||
|
||||
@@ -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 />,
|
||||
@@ -590,6 +618,16 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
|
||||
/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementAppSetting.tsx'
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
path: 'mcp',
|
||||
key: 'consumerMcp',
|
||||
lazy: lazy(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/mcpContent.tsx'
|
||||
)
|
||||
)
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -665,6 +703,14 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
'auth',
|
||||
{
|
||||
type: 'module',
|
||||
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/auth/Auth.tsx')),
|
||||
key: 'auth'
|
||||
}
|
||||
],
|
||||
[
|
||||
'analytics',
|
||||
{
|
||||
@@ -674,12 +720,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: [
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ export type MemberTableListItem = {
|
||||
enable:boolean
|
||||
departmentId:string
|
||||
roles:EntityItem[]
|
||||
from: string
|
||||
};
|
||||
|
||||
export type AddToDepartmentProps = {
|
||||
@@ -40,7 +41,7 @@ export type MemberDropdownModalFieldType = {
|
||||
|
||||
export type MemberDropdownModalProps = {
|
||||
type:'addDep'|'addChild'|'addMember'|'editMember'|'rename'
|
||||
entity?:(MemberTableListItem & {departmentIds:string[]}) | ({id?:string, departmentIds?:string[],name?:string})
|
||||
entity?:(MemberTableListItem & {departmentIds:string[]}) | ({id?:string, departmentIds?:string[],name?:string,from?:string})
|
||||
selectedMemberGroupId?:string
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ export type PartitionDataLogHeaderListFieldType = {
|
||||
export type PartitionDataLogConfigFieldType = {
|
||||
headers: PartitionDataLogHeaderListFieldType[]
|
||||
url: string
|
||||
driver?: string
|
||||
}
|
||||
|
||||
export const PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS: PageProColumns<PartitionDataLogConfigFieldType & { _id: string }>[] = [
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from './type'
|
||||
|
||||
import { PageProColumns } from '@common/components/aoplatform/PageList'
|
||||
import { LogItem } from '@core/pages/serviceLogs/ServiceLogs'
|
||||
|
||||
export enum SubscribeEnum {
|
||||
Rejected = 0,
|
||||
@@ -241,6 +242,12 @@ export const MATCH_CONFIG: ConfigField<MatchItem>[] = [
|
||||
]
|
||||
|
||||
export const SYSTEM_API_TABLE_COLUMNS: PageProColumns<SystemApiTableListItem>[] = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'URL',
|
||||
dataIndex: 'requestPath',
|
||||
@@ -500,3 +507,133 @@ export const SYSTEM_PUBLISH_ONLINE_COLUMNS = [
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
/** AI 服务排行 */
|
||||
export const AI_SERVICE_TOP_RANKING_LIST: PageProColumns<any>[] = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '请求总数',
|
||||
dataIndex: 'request',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Token',
|
||||
dataIndex: 'token',
|
||||
ellipsis: true
|
||||
}
|
||||
]
|
||||
|
||||
/** REST 服务排行 */
|
||||
export const REST_SERVICE_TOP_RANKING_LIST: PageProColumns<any>[] = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '请求总数',
|
||||
dataIndex: 'request',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '流量',
|
||||
dataIndex: 'traffic',
|
||||
ellipsis: true
|
||||
}
|
||||
]
|
||||
|
||||
/** REST 服务日志 */
|
||||
export const REST_SERVICE_LOG_LIST: PageProColumns<LogItem>[] = [
|
||||
{
|
||||
title: '时间戳',
|
||||
dataIndex: 'logTime',
|
||||
copyable: false,
|
||||
width: 180,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'API / Tools',
|
||||
dataIndex: ['api', 'name'],
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '消费者',
|
||||
dataIndex: ['consumer', 'name'],
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'HTTP 状态',
|
||||
dataIndex: 'status',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'IP',
|
||||
dataIndex: 'ip',
|
||||
copyable: true,
|
||||
width: 140,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '响应时间',
|
||||
dataIndex: 'responseTime',
|
||||
width: 130,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '流量',
|
||||
dataIndex: 'traffic',
|
||||
ellipsis: true
|
||||
}
|
||||
]
|
||||
|
||||
/** AI 服务日志 */
|
||||
export const AI_SERVICE_LOG_LIST: PageProColumns<LogItem>[] = [
|
||||
{
|
||||
title: '时间戳',
|
||||
dataIndex: 'logTime',
|
||||
copyable: false,
|
||||
width: 200,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'API / Tools',
|
||||
dataIndex: ['api', 'name'],
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '消费者',
|
||||
dataIndex: ['consumer', 'name'],
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'HTTP 状态',
|
||||
dataIndex: 'status',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '模型',
|
||||
dataIndex: 'model',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'IP',
|
||||
dataIndex: 'ip',
|
||||
copyable: true,
|
||||
width: 140,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Token/s',
|
||||
dataIndex: 'tokenPerSecond',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Token',
|
||||
dataIndex: 'token',
|
||||
ellipsis: true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -97,6 +97,7 @@ export type SystemApiProxyType = {
|
||||
export type SystemApiProxyFieldType = {
|
||||
protocols: string[];
|
||||
id:string;
|
||||
name:string
|
||||
description?:string;
|
||||
disable:boolean;
|
||||
path:string;
|
||||
|
||||
@@ -1156,9 +1156,32 @@ p{
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ranking-list .ant-pro-table{
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
border: none !important;
|
||||
}
|
||||
.ranking-list .ant-table-tbody:not(tbody) .ant-table-cell{
|
||||
padding: 10px 10px !important;
|
||||
}
|
||||
.ranking-list .ant-table-container .ant-table-thead th{
|
||||
background-color: #fff !important;
|
||||
padding: 10px 10px !important;
|
||||
}
|
||||
.ranking-list .ant-table-container .ant-table-thead th::before{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-alert-info{
|
||||
background: #1784FC1A !important;
|
||||
}
|
||||
.service-log-tab .ant-tabs .ant-tabs-nav .ant-tabs-tab{
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.service-log-tab .ant-tabs .ant-tabs-tab+.ant-tabs-tab {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.monaco-editor .find-widget .monaco-inputbox.synthetic-focus{
|
||||
outline-color: var(--primary-color) !important;
|
||||
|
||||
@@ -1,221 +1,405 @@
|
||||
import {FC, useCallback, useEffect, useRef, useState} from "react";
|
||||
import {App, Button, Divider, Form, FormInstance, Input, Spin, Tooltip} from "antd";
|
||||
import {useGlobalContext} from "@common/contexts/GlobalStateContext.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {BasicResponse, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { App, Button, Divider, Form, FormInstance, Input, Spin, Tooltip } from 'antd'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { BasicResponse, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
// import {useCrypto} from "../hooks/crypto.ts";
|
||||
import Logo from '@common/assets/layout-logo.png'
|
||||
import { $t } from "@common/locales";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import LanguageSetting from "@common/components/aoplatform/LanguageSetting";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import FeishuLogo from '@common/assets/feishu.png'
|
||||
import { $t } from '@common/locales'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import LanguageSetting from '@common/components/aoplatform/LanguageSetting'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
|
||||
const Login:FC = ()=> {
|
||||
const {state, dispatch} = useGlobalContext()
|
||||
const {fetchData} = useFetch()
|
||||
const { message } = App.useApp()
|
||||
const navigate = useNavigate();
|
||||
const formRef = useRef<FormInstance>(null);
|
||||
const [loading,setLoading] = useState<boolean>()
|
||||
const [allowGuest, setAllowGuest] = useState<boolean>(false)
|
||||
const [spinning,setSpinning] = useState<boolean>(false)
|
||||
const Login: FC = () => {
|
||||
const { state, dispatch } = useGlobalContext()
|
||||
const { fetchData } = useFetch()
|
||||
const { message } = App.useApp()
|
||||
const navigate = useNavigate()
|
||||
const formRef = useRef<FormInstance>(null)
|
||||
const [loading, setLoading] = useState<boolean>()
|
||||
const [allowGuest, setAllowGuest] = useState<boolean>(false)
|
||||
const [spinning, setSpinning] = useState<boolean>(false)
|
||||
// 是否允许飞书登录
|
||||
const [allowFeishuLogin, setAllowFeishuLogin] = useState<boolean>(false)
|
||||
// 飞书登录app_id
|
||||
const [feishuAppId, setFeishuAppId] = useState<string>()
|
||||
// 获取 url 参数
|
||||
const query = new URLSearchParams(useLocation().search)
|
||||
// 是否是飞书登录
|
||||
const [isFeishuLogin, setIsFeishuLogin] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (isFeishuLogin) {
|
||||
const callbackUrl = new URLSearchParams(window.location.search).get('callbackUrl')
|
||||
if (callbackUrl && callbackUrl !== 'null') {
|
||||
navigate(callbackUrl)
|
||||
} else {
|
||||
navigate(state.mainPage)
|
||||
}
|
||||
setIsFeishuLogin(false)
|
||||
}
|
||||
}, [isFeishuLogin])
|
||||
/**
|
||||
* 飞书登录
|
||||
* @param feishuCode 飞书 code
|
||||
*/
|
||||
const feishuLogin = async (feishuCode: string) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const feishuCallbackUrl = localStorage.getItem('feishuCallbackUrl')
|
||||
const { code, msg } = await fetchData<BasicResponse<null>>('account/login/feishu', {
|
||||
method: 'POST',
|
||||
eoBody: {
|
||||
code: feishuCode,
|
||||
redirect_uri: feishuCallbackUrl
|
||||
}
|
||||
})
|
||||
|
||||
const check = useCallback(()=>{
|
||||
state.isAuthenticated &&setSpinning(true)
|
||||
fetchData<BasicResponse<{channel:Array<{name:string}>, status:string}>>('account/login',{method:'GET'}).then(response=>{
|
||||
const {code,data} = response
|
||||
if(code === STATUS_CODE.SUCCESS && data.status !== 'anonymous'){
|
||||
dispatch({type:'LOGIN'})
|
||||
navigate(state.mainPage,{replace:true})
|
||||
}else{
|
||||
dispatch({type:'LOGOUT'})
|
||||
setAllowGuest(data.channel.filter(x=>x.name === 'guest_access').length > 0)
|
||||
setSpinning(false)
|
||||
}
|
||||
})
|
||||
},[])
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
dispatch({ type: 'LOGIN' })
|
||||
setIsFeishuLogin(true)
|
||||
} else {
|
||||
dispatch({ type: 'LOGOUT' })
|
||||
setIsFeishuLogin(false)
|
||||
message.error(msg)
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const check = useCallback(() => {
|
||||
state.isAuthenticated && setSpinning(true)
|
||||
fetchData<BasicResponse<{ channel: Array<{ name: string; config: { [key: string]: any } }>; status: string }>>(
|
||||
'account/login',
|
||||
{ method: 'GET' }
|
||||
).then((response) => {
|
||||
const { code, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS && data.status !== 'anonymous') {
|
||||
dispatch({ type: 'LOGIN' })
|
||||
navigate(state.mainPage, { replace: true })
|
||||
} else {
|
||||
dispatch({ type: 'LOGOUT' })
|
||||
setAllowGuest(data.channel.filter((x: any) => x.name === 'guest_access').length > 0)
|
||||
const feishu = data.channel.find((x: any) => x.name === 'feishu')
|
||||
if (feishu) {
|
||||
setFeishuAppId(feishu.config.client_id)
|
||||
setAllowFeishuLogin(true)
|
||||
}
|
||||
const code = query.get('code')
|
||||
if (code) {
|
||||
feishuLogin(code)
|
||||
setSpinning(false)
|
||||
return
|
||||
}
|
||||
if (isInFeishuClient() && feishu) {
|
||||
openFeishuLogin(feishu.config.client_id)
|
||||
}
|
||||
setSpinning(false)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
const getSystemInfo = useCallback(() => {
|
||||
fetchData<BasicResponse<{ version: string; buildTime: string }>>('common/version', {
|
||||
method: 'GET',
|
||||
eoTransformKeys: ['build_time']
|
||||
}).then((response) => {
|
||||
const { code, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
dispatch({ type: 'UPDATE_VERSION', version: data.version })
|
||||
dispatch({ type: 'UPDATE_DATE', updateDate: data.buildTime })
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
const fetchLogin = async (values: any) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const { username, password } = values
|
||||
// const encryptedPassword = encryptByEnAES(username, password);
|
||||
|
||||
const body = {
|
||||
name: username,
|
||||
password: password
|
||||
// client: 1,
|
||||
// type: 1,
|
||||
// app_type: 4,
|
||||
}
|
||||
|
||||
const { code, msg } = await fetchData<BasicResponse<null>>('account/login/username', {
|
||||
method: 'POST',
|
||||
eoBody: body
|
||||
})
|
||||
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
dispatch({ type: 'LOGIN' })
|
||||
// message.success($t(RESPONSE_TIPS.loginSuccess));
|
||||
const callbackUrl = new URLSearchParams(window.location.search).get('callbackUrl')
|
||||
if (callbackUrl && callbackUrl !== 'null') {
|
||||
navigate(callbackUrl)
|
||||
} else {
|
||||
navigate(state.mainPage)
|
||||
}
|
||||
} else {
|
||||
dispatch({ type: 'LOGOUT' })
|
||||
message.error(msg)
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
const login = async () => {
|
||||
if (formRef.current) {
|
||||
const values = await formRef.current.validateFields()
|
||||
fetchLogin(values)
|
||||
}
|
||||
}
|
||||
|
||||
const loginAsGuest = () => {
|
||||
fetchLogin({ username: 'guest', password: '12345678' })
|
||||
}
|
||||
|
||||
const isInFeishuClient = () => {
|
||||
// 方法1:检查User-Agent
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
const isLark = ua.includes('lark') || ua.includes('feishu');
|
||||
|
||||
const getSystemInfo = useCallback(()=>{
|
||||
fetchData<BasicResponse<{version:string, buildTime:string}>>('common/version',{method:'GET', eoTransformKeys:['build_time']}).then(response=>{
|
||||
const {code,data} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
dispatch({type:'UPDATE_VERSION',version:data.version})
|
||||
dispatch({type:'UPDATE_DATE',updateDate:data.buildTime})
|
||||
}
|
||||
})
|
||||
},[])
|
||||
// 方法2:检查全局对象
|
||||
const hasSDK = typeof window.h5sdk !== 'undefined' || typeof window.tt !== 'undefined';
|
||||
|
||||
// 方法3:检查URL参数
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const hasFeishuParams = params.has('from') || params.has('required_launch_ability');
|
||||
|
||||
return isLark || hasSDK || hasFeishuParams;
|
||||
}
|
||||
|
||||
// 打开飞书授权页面
|
||||
const openFeishuLogin = (id?: string) => {
|
||||
const href = window.location.origin + window.location.pathname
|
||||
const authUrl = `https://accounts.feishu.cn/open-apis/authen/v1/authorize?client_id=${id || feishuAppId}&redirect_uri=${href}`
|
||||
localStorage.setItem('feishuCallbackUrl', href)
|
||||
window.location.href = authUrl
|
||||
}
|
||||
|
||||
const fetchLogin = async (values:any)=>{
|
||||
try {
|
||||
setLoading(true);
|
||||
const { username, password } = values;
|
||||
// const encryptedPassword = encryptByEnAES(username, password);
|
||||
useEffect(() => {
|
||||
check()
|
||||
getSystemInfo()
|
||||
}, [])
|
||||
|
||||
const body = {
|
||||
name:username,
|
||||
password: password
|
||||
// client: 1,
|
||||
// type: 1,
|
||||
// app_type: 4,
|
||||
};
|
||||
return spinning ? (
|
||||
<Spin
|
||||
indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
|
||||
spinning={spinning}
|
||||
className="w-full h-full flex items-center justify-center"
|
||||
></Spin>
|
||||
) : (
|
||||
<div className="h-full w-full flex flex-col items-center overflow-auto min-h-[490px] bg-[#0d1117]">
|
||||
<div id="glow-background" className="background-container">
|
||||
<svg className="background-pattern" aria-hidden="true">
|
||||
<defs>
|
||||
<pattern id="pattern-bg" width="200" height="200" patternUnits="userSpaceOnUse">
|
||||
<path d="M.5 200V.5H200" fill="none"></path>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#pattern-bg)"></rect>
|
||||
</svg>
|
||||
|
||||
const {code,msg } = await fetchData<BasicResponse<null>>('account/login/username',{method:'POST',eoBody:(body)})
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:svgjs="http://svgjs.dev/svgjs"
|
||||
viewBox="0 0 800 450"
|
||||
opacity="1"
|
||||
>
|
||||
<defs>
|
||||
<filter
|
||||
id="bbblurry-filter"
|
||||
x="-100%"
|
||||
y="-100%"
|
||||
width="400%"
|
||||
height="400%"
|
||||
filterUnits="objectBoundingBox"
|
||||
primitiveUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feGaussianBlur
|
||||
stdDeviation="99"
|
||||
x="0%"
|
||||
y="0%"
|
||||
width="100%"
|
||||
height="100%"
|
||||
in="SourceGraphic"
|
||||
edgeMode="none"
|
||||
result="blur"
|
||||
></feGaussianBlur>
|
||||
</filter>
|
||||
</defs>
|
||||
<g filter="url(#bbblurry-filter)">
|
||||
<ellipse
|
||||
rx="80.5"
|
||||
ry="66.5"
|
||||
cx="623.0285107902043"
|
||||
cy="25.708028895006635"
|
||||
fill="hsla(187, 67%, 50%, 1.00)"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="hsla(187, 67%, 50%, 1.00); hsla(340, 85%, 60%, 1.00); hsla(60, 90%, 55%, 1.00); hsla(187, 67%, 50%, 1.00)"
|
||||
dur="6s"
|
||||
repeatCount="indefinite"
|
||||
></animate>
|
||||
</ellipse>
|
||||
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
dispatch({type:'LOGIN'})
|
||||
// message.success($t(RESPONSE_TIPS.loginSuccess));
|
||||
const callbackUrl = new URLSearchParams(window.location.search).get('callbackUrl');
|
||||
if (callbackUrl && callbackUrl !== 'null') {
|
||||
navigate(callbackUrl);
|
||||
} else {
|
||||
navigate(state.mainPage);
|
||||
}
|
||||
}else{
|
||||
dispatch({type:'LOGOUT'})
|
||||
message.error(msg)
|
||||
}
|
||||
<ellipse
|
||||
rx="80.5"
|
||||
ry="66.5"
|
||||
cx="446.471435546875"
|
||||
cy="-11.694503784179688"
|
||||
fill="hsla(234, 78%, 61%, 1.00)"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="hsla(234, 78%, 61%, 1.00); hsla(100, 75%, 60%, 1.00); hsla(290, 80%, 70%, 1.00); hsla(234, 78%, 61%, 1.00)"
|
||||
dur="8s"
|
||||
repeatCount="indefinite"
|
||||
></animate>
|
||||
</ellipse>
|
||||
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
const login = async () => {
|
||||
if (formRef.current) {
|
||||
const values = await formRef.current.validateFields();
|
||||
fetchLogin(values);
|
||||
}
|
||||
};
|
||||
<ellipse
|
||||
rx="80.5"
|
||||
ry="66.5"
|
||||
cx="200.54574247724838"
|
||||
cy="-19.02454901710908"
|
||||
fill="hsla(167, 87%, 56%, 1.00)"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="hsla(167, 87%, 56%, 1.00); hsla(10, 90%, 65%, 1.00); hsla(300, 85%, 50%, 1.00); hsla(167, 87%, 56%, 1.00)"
|
||||
dur="10s"
|
||||
repeatCount="indefinite"
|
||||
></animate>
|
||||
</ellipse>
|
||||
|
||||
const loginAsGuest = ()=>{
|
||||
fetchLogin({username:'guest',password:'12345678'})
|
||||
}
|
||||
<ellipse rx="80.5" ry="66.5" cx="340.05827594708103" cy="-9.424536458161867" fill="hsl(25, 100%, 64%)">
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="hsl(25, 100%, 64%); hsl(200, 100%, 70%); hsl(50, 95%, 55%); hsl(25, 100%, 64%)"
|
||||
dur="8s"
|
||||
repeatCount="indefinite"
|
||||
></animate>
|
||||
</ellipse>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
{/* <div className="w-full border-box text-right pr-[40px]"></div> */}
|
||||
<div className="mx-auto flex-1 flex flex-col items-center justify-center z-[3]">
|
||||
<div className="mx-auto">
|
||||
<span className="flex items-center justify-center">
|
||||
<img className="h-[40px] mr-[8px]" src={Logo} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
useEffect(() => {
|
||||
check()
|
||||
getSystemInfo()
|
||||
}, []);
|
||||
<section className="block w-[410px] mx-auto mt-[46px] p-[30px] box-border rounded-[10px] shadow-[0_5px_20px_0_rgba(0,0,0,5%)] login-block">
|
||||
<div className="h-full">
|
||||
<div className="">
|
||||
<Form onFinish={login} className="w-[350px]" ref={formRef}>
|
||||
<Form.Item
|
||||
className="p-0 bg-transparent rounded border-none"
|
||||
name="username"
|
||||
rules={[{ required: true, message: $t('请输入账号'), whitespace: true }]}
|
||||
>
|
||||
<Input
|
||||
className="w-[350px] h-[40px] login-input"
|
||||
placeholder={$t('账号')}
|
||||
autoComplete="on"
|
||||
autoFocus
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
return (
|
||||
spinning?
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={spinning} className='w-full h-full flex items-center justify-center'></Spin> :
|
||||
<div className="h-full w-full flex flex-col items-center overflow-auto min-h-[490px] bg-[#0d1117]">
|
||||
<div id="glow-background" className="background-container">
|
||||
<svg className="background-pattern" aria-hidden="true">
|
||||
<defs>
|
||||
<pattern id="pattern-bg" width="200" height="200" patternUnits="userSpaceOnUse">
|
||||
<path d="M.5 200V.5H200" fill="none"></path>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#pattern-bg)"></rect>
|
||||
</svg>
|
||||
<Form.Item
|
||||
className="p-0 bg-transparent rounded border-none "
|
||||
name="password"
|
||||
rules={[{ required: true, message: $t('请输入密码') }]}
|
||||
>
|
||||
<Input.Password
|
||||
className="w-[350px] h-[40px] login-input"
|
||||
placeholder={$t('密码')}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" viewBox="0 0 800 450" opacity="1">
|
||||
<defs>
|
||||
<filter id="bbblurry-filter" x="-100%" y="-100%" width="400%" height="400%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur stdDeviation="99" x="0%" y="0%" width="100%" height="100%" in="SourceGraphic" edgeMode="none" result="blur"></feGaussianBlur>
|
||||
</filter>
|
||||
</defs>
|
||||
<g filter="url(#bbblurry-filter)">
|
||||
<ellipse rx="80.5" ry="66.5" cx="623.0285107902043" cy="25.708028895006635" fill="hsla(187, 67%, 50%, 1.00)">
|
||||
<animate attributeName="fill" values="hsla(187, 67%, 50%, 1.00); hsla(340, 85%, 60%, 1.00); hsla(60, 90%, 55%, 1.00); hsla(187, 67%, 50%, 1.00)" dur="6s" repeatCount="indefinite"></animate>
|
||||
</ellipse>
|
||||
|
||||
<ellipse rx="80.5" ry="66.5" cx="446.471435546875" cy="-11.694503784179688" fill="hsla(234, 78%, 61%, 1.00)">
|
||||
<animate attributeName="fill" values="hsla(234, 78%, 61%, 1.00); hsla(100, 75%, 60%, 1.00); hsla(290, 80%, 70%, 1.00); hsla(234, 78%, 61%, 1.00)" dur="8s" repeatCount="indefinite"></animate>
|
||||
</ellipse>
|
||||
|
||||
<ellipse rx="80.5" ry="66.5" cx="200.54574247724838" cy="-19.02454901710908" fill="hsla(167, 87%, 56%, 1.00)">
|
||||
<animate attributeName="fill" values="hsla(167, 87%, 56%, 1.00); hsla(10, 90%, 65%, 1.00); hsla(300, 85%, 50%, 1.00); hsla(167, 87%, 56%, 1.00)" dur="10s" repeatCount="indefinite"></animate>
|
||||
</ellipse>
|
||||
|
||||
<ellipse rx="80.5" ry="66.5" cx="340.05827594708103" cy="-9.424536458161867" fill="hsl(25, 100%, 64%)">
|
||||
<animate attributeName="fill" values="hsl(25, 100%, 64%); hsl(200, 100%, 70%); hsl(50, 95%, 55%); hsl(25, 100%, 64%)" dur="8s" repeatCount="indefinite"></animate>
|
||||
</ellipse>
|
||||
</g>
|
||||
</svg>
|
||||
<Form.Item className="p-0 bg-transparent rounded border-none ">
|
||||
<Button
|
||||
loading={loading}
|
||||
className="h-[40px] mt-mbase w-full inline-flex justify-center items-center"
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
>
|
||||
{$t('登录')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
{allowFeishuLogin && (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<Form.Item className="p-0 bg-transparent rounded border-none mb-0">
|
||||
<Button
|
||||
loading={loading}
|
||||
className="h-[40px] w-full inline-flex justify-center items-center"
|
||||
type="default"
|
||||
onClick={() => openFeishuLogin(feishuAppId)}
|
||||
>
|
||||
<img className="h-[30px]" src={FeishuLogo} />
|
||||
{$t('飞书授权登录')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{allowGuest && (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<Form.Item className="p-0 bg-transparent rounded border-none mb-0">
|
||||
<Button
|
||||
loading={loading}
|
||||
className="h-[40px] w-full inline-flex justify-center items-center"
|
||||
type="default"
|
||||
onClick={loginAsGuest}
|
||||
>
|
||||
{$t('访客模式')}{' '}
|
||||
<Tooltip
|
||||
title={$t(
|
||||
'您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。'
|
||||
)}
|
||||
>
|
||||
<Icon icon="ic:baseline-help" height={18} width={18} />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
{/* <div className="w-full border-box text-right pr-[40px]"></div> */}
|
||||
<div className="mx-auto flex-1 flex flex-col items-center justify-center z-[3]" >
|
||||
<div className="mx-auto">
|
||||
<span className="flex items-center justify-center">
|
||||
<img
|
||||
className="h-[40px] mr-[8px]"
|
||||
src={Logo}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="block w-[410px] mx-auto mt-[46px] p-[30px] box-border rounded-[10px] shadow-[0_5px_20px_0_rgba(0,0,0,5%)] login-block">
|
||||
<div className="h-full">
|
||||
<div className="">
|
||||
<Form onFinish={login} className="w-[350px]"
|
||||
ref={formRef}>
|
||||
<Form.Item
|
||||
className="p-0 bg-transparent rounded border-none"
|
||||
name="username"
|
||||
rules={[{ required: true, message: $t('请输入账号') ,whitespace:true }]}
|
||||
>
|
||||
<Input
|
||||
className="w-[350px] h-[40px] login-input"
|
||||
placeholder={$t("账号")}
|
||||
autoComplete="on"
|
||||
autoFocus
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
className="p-0 bg-transparent rounded border-none "
|
||||
name="password"
|
||||
rules={[{ required: true, message: $t('请输入密码') }]}
|
||||
>
|
||||
<Input.Password
|
||||
className="w-[350px] h-[40px] login-input"
|
||||
placeholder={$t("密码")}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
className="p-0 bg-transparent rounded border-none "
|
||||
>
|
||||
<Button loading={loading} className="h-[40px] mt-mbase w-full inline-flex justify-center items-center" type="primary" htmlType="submit">
|
||||
{$t('登录')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
{
|
||||
allowGuest && <>
|
||||
<Divider />
|
||||
|
||||
<Form.Item
|
||||
className="p-0 bg-transparent rounded border-none mb-0"
|
||||
>
|
||||
<Button loading={loading} className="h-[40px] w-full inline-flex justify-center items-center" type="default" onClick={loginAsGuest}>
|
||||
{$t('访客模式')} <Tooltip title={$t('您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。')}><Icon icon="ic:baseline-help" height={18} width={18} /></Tooltip>
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
}
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col items-center mt-[46px] text-SECOND_TEXT">
|
||||
<p className="leading-[28px]">
|
||||
{$t('Version (0)-(1)',[state?.version,state?.updateDate])}, {$t(state?.powered || '-')}
|
||||
</p>
|
||||
<LanguageSetting mode="light"/>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<section className="flex flex-col items-center mt-[46px] text-SECOND_TEXT">
|
||||
<p className="leading-[28px]">
|
||||
{$t('Version (0)-(1)', [state?.version, state?.updateDate])}, {$t(state?.powered || '-')}
|
||||
</p>
|
||||
<LanguageSetting mode="light" />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Login;
|
||||
export default Login
|
||||
|
||||
@@ -8,9 +8,8 @@ import { App, Button } from 'antd'
|
||||
import { EntityItem } from '@common/const/type.ts'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { $t } from '@common/locales'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
|
||||
const ServiceInsideDocument = () => {
|
||||
const { message } = App.useApp()
|
||||
const [updater, setUpdater] = useState<string>()
|
||||
@@ -19,8 +18,6 @@ const ServiceInsideDocument = () => {
|
||||
const [doc, setDoc] = useState<string>()
|
||||
const { fetchData } = useFetch()
|
||||
const { serviceId, teamId } = useParams<RouterParams>()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const navigator = useNavigate()
|
||||
|
||||
const save = () => {
|
||||
fetchData<
|
||||
@@ -80,15 +77,6 @@ const ServiceInsideDocument = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('使用说明')
|
||||
}
|
||||
])
|
||||
getServiceDoc()
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -9,11 +9,12 @@ import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import { AiServiceConfigFieldType } from '@core/const/ai-service/type.ts'
|
||||
import { App, Menu, MenuProps } from 'antd'
|
||||
import { ItemType, MenuItemGroupType, MenuItemType } from 'antd/es/menu/interface'
|
||||
import Paragraph from 'antd/es/typography/Paragraph'
|
||||
import ServiceInfoCard from '@common/components/aoplatform/serviceInfoCard.tsx'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { FC, useEffect, useMemo, useState } from 'react'
|
||||
import { Link, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
import { useAiServiceContext } from '../../contexts/AiServiceContext.tsx'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
const APP_MODE = import.meta.env.VITE_APP_MODE
|
||||
|
||||
const AiServiceInsidePage: FC = () => {
|
||||
@@ -27,6 +28,7 @@ const AiServiceInsidePage: FC = () => {
|
||||
const [activeMenu, setActiveMenu] = useState<string>()
|
||||
const navigateTo = useNavigate()
|
||||
const [showMenu, setShowMenu] = useState<boolean>(false)
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
|
||||
const getAiServiceInfo = () => {
|
||||
fetchData<BasicResponse<{ service: AiServiceConfigFieldType }>>('service/info', {
|
||||
@@ -67,6 +69,7 @@ const AiServiceInsidePage: FC = () => {
|
||||
'assets',
|
||||
null,
|
||||
[
|
||||
getItem(<Link to="./overview">{$t('总览')}</Link>, 'overview', undefined, undefined, undefined, ''),
|
||||
getItem(
|
||||
<Link to="./route">{$t('API 路由')}</Link>,
|
||||
'route',
|
||||
@@ -149,7 +152,8 @@ const AiServiceInsidePage: FC = () => {
|
||||
'project.myAiService.topology.view'
|
||||
)
|
||||
: null,
|
||||
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting', undefined, undefined, undefined, '')
|
||||
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting', undefined, undefined, undefined, ''),
|
||||
getItem(<Link to="./logs">{$t('日志')}</Link>, 'logs', undefined, undefined, undefined, '')
|
||||
],
|
||||
'group'
|
||||
)
|
||||
@@ -202,7 +206,7 @@ const AiServiceInsidePage: FC = () => {
|
||||
} else if (serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]) {
|
||||
setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1])
|
||||
} else {
|
||||
setActiveMenu('route')
|
||||
setActiveMenu('overview')
|
||||
}
|
||||
}, [currentUrl])
|
||||
|
||||
@@ -213,10 +217,19 @@ const AiServiceInsidePage: FC = () => {
|
||||
}, [accessData])
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigateTo('/service/list')
|
||||
},
|
||||
{
|
||||
title: aiServiceInfo?.name || ''
|
||||
}
|
||||
])
|
||||
if (activeMenu && serviceId === currentUrl.split('/')[currentUrl.split('/').length - 1]) {
|
||||
navigateTo(`/service/${teamId}/aiInside/${serviceId}/${activeMenu}`)
|
||||
}
|
||||
}, [activeMenu])
|
||||
}, [activeMenu, state.language, aiServiceInfo])
|
||||
|
||||
useEffect(() => {
|
||||
serviceId && getAiServiceInfo()
|
||||
@@ -231,17 +244,8 @@ const AiServiceInsidePage: FC = () => {
|
||||
{showMenu ? (
|
||||
<InsidePage
|
||||
pageTitle={aiServiceInfo?.name || '-'}
|
||||
tagList={[
|
||||
...(aiServiceInfo?.enable_mcp ? [{ label: 'MCP', color: '#FFF0C1', className: 'text-[#000]' }] : []),
|
||||
{
|
||||
label: (
|
||||
<Paragraph className="mb-0" copyable={serviceId ? { text: serviceId } : false}>
|
||||
{$t('服务 ID')}:{serviceId || '-'}
|
||||
</Paragraph>
|
||||
)
|
||||
}
|
||||
]}
|
||||
backUrl="/service/list"
|
||||
customBanner={<ServiceInfoCard serviceId={serviceId} teamId={teamId} />}
|
||||
>
|
||||
<div className="flex flex-1 h-full">
|
||||
<Menu
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import {ActionType} from "@ant-design/pro-components";
|
||||
import {FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react";
|
||||
import {Link, useNavigate, useParams} from "react-router-dom";
|
||||
import {useParams} from "react-router-dom";
|
||||
import {App, Form,TreeSelect} from "antd";
|
||||
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx";
|
||||
import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx";
|
||||
@@ -18,7 +17,6 @@ import { checkAccess } from "@common/utils/permission.ts";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
|
||||
const AiServiceInsideSubscriber:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal,message } = App.useApp()
|
||||
const {fetchData} = useFetch()
|
||||
const {serviceId, teamId} = useParams<RouterParams>()
|
||||
@@ -26,7 +24,6 @@ const AiServiceInsideSubscriber:FC = ()=>{
|
||||
const pageListRef = useRef<ActionType>(null);
|
||||
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
|
||||
const {accessData,state} = useGlobalContext()
|
||||
const navigator = useNavigate()
|
||||
const getAiServiceSubscriber = ()=>{
|
||||
return fetchData<BasicResponse<{subscribers:AiServiceSubscriberTableListItem[]}>>('service/subscribers',{method:'GET',eoParams:{service:serviceId,team:teamId},eoTransformKeys:['apply_time']}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
@@ -120,15 +117,6 @@ const AiServiceInsideSubscriber:FC = ()=>{
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('订阅方管理')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
@@ -6,33 +6,21 @@ import { LoadingOutlined } from '@ant-design/icons'
|
||||
import EmptySVG from '@common/assets/empty.svg'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import ApiDocument from '@common/components/aoplatform/ApiDocument.tsx'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import {
|
||||
AiServiceInsideApiDocumentHandle,
|
||||
AiServiceInsideApiDocumentProps,
|
||||
AiServiceApiDetail
|
||||
} from '@core/const/ai-service/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
|
||||
|
||||
const AiServiceInsideApiDocument = forwardRef<AiServiceInsideApiDocumentHandle, AiServiceInsideApiDocumentProps>(() => {
|
||||
const { serviceId, teamId } = useParams<RouterParams>()
|
||||
const { fetchData } = useFetch()
|
||||
const [apiDetail, setApiDetail] = useState<AiServiceApiDetail>()
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const navigator = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('API 文档')
|
||||
}
|
||||
])
|
||||
getApiDetail()
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -291,19 +291,6 @@ const AiServiceInsideRouterCreate = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('API'),
|
||||
onClick: () => navigator(backUrl)
|
||||
},
|
||||
{
|
||||
title: routeId ? $t('编辑 API') : $t('添加 API')
|
||||
}
|
||||
])
|
||||
!routeId && aiServiceInfo?.provider && getDefaultModelConfig()
|
||||
}, [aiServiceInfo])
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import PageList, { PageProColumns } from '@common/components/aoplatform/PageList
|
||||
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx'
|
||||
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { SimpleMemberItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
@@ -17,7 +16,6 @@ import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
const AiServiceInsideRouterList: FC = () => {
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal, message } = App.useApp()
|
||||
const [tableListDataSource, setTableListDataSource] = useState<AiServiceRouterTableListItem[]>([])
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true)
|
||||
@@ -162,17 +160,6 @@ const AiServiceInsideRouterList: FC = () => {
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId])
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('路由')
|
||||
}
|
||||
])
|
||||
}, [state.language])
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return [...AI_SERVICE_ROUTER_TABLE_COLUMNS].map((x) => {
|
||||
|
||||
@@ -3,7 +3,6 @@ import {ActionType} from "@ant-design/pro-components";
|
||||
import {FC, useEffect, useMemo, useRef, useState} from "react";
|
||||
import {Link, useLocation, useNavigate, useParams} from "react-router-dom";
|
||||
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx";
|
||||
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {App, Button} from "antd";
|
||||
import {
|
||||
SUBSCRIBE_APPROVAL_INNER_DONE_TABLE_COLUMN,
|
||||
@@ -26,7 +25,6 @@ import { SubscribeApprovalInfoType } from "@common/const/approval/type.tsx";
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
const AiServiceInsideApprovalList:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal,message } = App.useApp()
|
||||
const {serviceId, teamId} = useParams<RouterParams>();
|
||||
const [init, setInit] = useState<boolean>(true)
|
||||
@@ -40,7 +38,6 @@ const AiServiceInsideApprovalList:FC = ()=>{
|
||||
const [approvalBtnLoading,setApprovalBtnLoading] = useState<boolean>(false)
|
||||
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
|
||||
const {accessData,state} = useGlobalContext()
|
||||
const navigator = useNavigate()
|
||||
|
||||
const openModal = async (type:'approval'|'view',entity:SubscribeApprovalTableListItem)=>{
|
||||
message.loading($t(RESPONSE_TIPS.loading))
|
||||
@@ -142,15 +139,6 @@ const AiServiceInsideApprovalList:FC = ()=>{
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('订阅审核')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
import { Tabs } from "antd"
|
||||
import { useState, useEffect, FC, useMemo } from "react"
|
||||
import { Link, Outlet, useLocation, useNavigate } from "react-router-dom"
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"
|
||||
import { SYSTEM_PUBLISH_TAB_ITEMS } from "../../../const/system/const"
|
||||
import { $t } from "@common/locales"
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
|
||||
|
||||
const AiServiceInsidePublic:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const query =new URLSearchParams(useLocation().search)
|
||||
const location = useLocation()
|
||||
const currentUrl = location.pathname
|
||||
@@ -25,18 +23,6 @@ const AiServiceInsidePublic:FC = ()=>{
|
||||
setPageStatus(Number(query.get('status') ||0) as 0|1)
|
||||
}, [currentUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigateTo('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('发布')
|
||||
}
|
||||
])
|
||||
}, []);
|
||||
|
||||
const tabItems = useMemo(()=>SYSTEM_PUBLISH_TAB_ITEMS?.map((x)=>({...x, label:$t(x.label as string) })),[state.language])
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -9,7 +9,6 @@ import { PUBLISH_APPROVAL_RECORD_INNER_TABLE_COLUMN, PUBLISH_APPROVAL_VERSION_IN
|
||||
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
|
||||
import { SimpleMemberItem } from "@common/const/type.ts";
|
||||
import { MemberTableListItem } from "../../../const/member/type";
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext";
|
||||
import { useFetch } from "@common/hooks/http";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission";
|
||||
import { AiServicePublishReleaseItem } from "../../../const/system/type";
|
||||
@@ -23,7 +22,6 @@ import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
const AiServiceInsidePublicList:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal,message } = App.useApp()
|
||||
const pageListRef = useRef<ActionType>(null);
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true);
|
||||
@@ -45,7 +43,6 @@ const AiServiceInsidePublicList:FC = ()=>{
|
||||
const [drawerData, setDrawerData] = useState<PublishTableListItem|PublishVersionTableListItem >({} as PublishTableListItem)
|
||||
const [drawerOkTitle, setDrawerOkTitle] = useState<string>('确认')
|
||||
const [isOkToPublish, setIsOkToPublish] = useState<boolean>(false)
|
||||
const navigator = useNavigate()
|
||||
const getAiServicePublishList = (params?: ParamsType & {
|
||||
pageSize?: number | undefined;
|
||||
current?: number | undefined;
|
||||
@@ -351,15 +348,6 @@ const AiServiceInsidePublicList:FC = ()=>{
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('发布')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import InsidePage from '@common/components/aoplatform/InsidePage'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { $t } from '@common/locales'
|
||||
import { App, Button, Form, Input, Row, Select, Switch } from 'antd'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
type AuthSetting = {
|
||||
config: {
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
}
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
type AuthFieldType = {
|
||||
authType: string
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
const Auth = () => {
|
||||
const { message } = App.useApp()
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
const [, forceUpdate] = useState<unknown>(null)
|
||||
const { state } = useGlobalContext()
|
||||
const [thirdPartyDrivers, setThirdPartyDrivers] = useState<{ label: string; value: string }[]>([])
|
||||
useEffect(() => {
|
||||
forceUpdate({})
|
||||
}, [state.language])
|
||||
const onFinish = () => {
|
||||
form.validateFields().then((value) => {
|
||||
return fetchData<BasicResponse<null>>(`account/third/${value.authType}`, {
|
||||
method: 'POST',
|
||||
eoBody: {
|
||||
enable: value.enabled,
|
||||
config: {
|
||||
client_id: value.clientId,
|
||||
client_secret: value.clientSecret
|
||||
}
|
||||
}
|
||||
})
|
||||
.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((errorInfo) => {
|
||||
return Promise.reject(errorInfo)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第三方授权列表
|
||||
*/
|
||||
const getThirdPartyAuthList = () => {
|
||||
fetchData<
|
||||
BasicResponse<{
|
||||
drivers: {
|
||||
name: string
|
||||
value: string
|
||||
}[]
|
||||
}>
|
||||
>('account/third', {
|
||||
method: 'GET',
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setThirdPartyDrivers(data.drivers.map((item: any) => ({ label: item.name, value: item.value })))
|
||||
if (data.drivers.length) {
|
||||
form.setFieldValue('authType', data.drivers[0].value)
|
||||
getThirdPartyAuthSetting()
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第三方授权配置
|
||||
*/
|
||||
const getThirdPartyAuthSetting = () => {
|
||||
fetchData<BasicResponse<{ info: AuthSetting }>>(`account/third/${form.getFieldValue('authType')}`, {
|
||||
method: 'GET',
|
||||
eoTransformKeys: ['client_id', 'client_secret']
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
form.setFieldsValue({
|
||||
clientId: data.driver?.config?.clientId || '',
|
||||
clientSecret: data.driver?.config?.clientSecret || '',
|
||||
enabled: data.driver?.enable || false
|
||||
})
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getThirdPartyAuthList()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<InsidePage pageTitle={$t('鉴权')} showBorder={false} contentClassName="pr-PAGE_INSIDE_X" scrollPage={false} description={$t("系统用户账号登录授权配置")}>
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
layout="vertical"
|
||||
labelAlign="left"
|
||||
scrollToFirstError
|
||||
form={form}
|
||||
className={`mx-auto`}
|
||||
name="authConfig"
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item<AuthFieldType>
|
||||
label={$t('授权类型')}
|
||||
name="authType"
|
||||
rules={[{ required: true, message: $t('请选择授权类型') }]}
|
||||
>
|
||||
<Select className="w-INPUT_NORMAL" placeholder={$t('请选择授权类型')} onChange={getThirdPartyAuthSetting} options={thirdPartyDrivers} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AuthFieldType>
|
||||
label={$t('APP ID')}
|
||||
name="clientId"
|
||||
rules={[{ required: true, whitespace: true, message: $t('请输入APP ID') }]}
|
||||
extra={$t('APP ID 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上')}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} autoComplete="off" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AuthFieldType>
|
||||
label={$t('APP Secret')}
|
||||
name="clientSecret"
|
||||
rules={[{ required: true, whitespace: true, message: $t('请输入APP Secret') }]}
|
||||
extra={$t('APP Secret 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上')}
|
||||
>
|
||||
<Input.Password className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} autoComplete="new-password" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AuthFieldType> label={$t('启用授权')} name="enabled" valuePropName="checked">
|
||||
<Switch checkedChildren={$t('启用')} unCheckedChildren={$t('停用')} />
|
||||
</Form.Item>
|
||||
|
||||
<Row className="mb-[10px]">
|
||||
<WithPermission access="system.devops.system_setting.edit">
|
||||
<Button type="primary" htmlType="submit">
|
||||
{$t('保存')}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
</Row>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</InsidePage>
|
||||
)
|
||||
}
|
||||
|
||||
export default Auth
|
||||
@@ -39,6 +39,7 @@ const ApiKeyContent: React.FC<ApiKeyContentProps> = forwardRef(({ provider, enti
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
// 表单校验
|
||||
const values = await form.validateFields()
|
||||
const { expire_time, ...restValues } = values
|
||||
const expireTime = neverExpire ? 0 : Math.trunc(expire_time.valueOf() / 1000)
|
||||
|
||||
@@ -68,7 +68,7 @@ const LogSettings = () => {
|
||||
<>
|
||||
<Skeleton className="m-btnbase w-calc-100vw-minus-padding-r" active loading={loading}>
|
||||
<InsidePage
|
||||
pageTitle={$t('日志配置')}
|
||||
pageTitle={$t('日志输出设置')}
|
||||
description={'APIPark ' + $t('提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。')}
|
||||
>
|
||||
<div className="flex h-full">
|
||||
|
||||
@@ -50,17 +50,23 @@ type ServiceApiKeyList = {
|
||||
expired: number
|
||||
}>
|
||||
}
|
||||
|
||||
type ConsumerParamsType = {
|
||||
consumerId: string
|
||||
teamId: string
|
||||
}
|
||||
export interface IntegrationAIContainerRef {
|
||||
getServiceKeysList: () => void;
|
||||
}
|
||||
export interface IntegrationAIContainerProps {
|
||||
type: 'global' | 'service'
|
||||
type: 'global' | 'service' | 'consumer'
|
||||
handleToolsChange: (value: Tool[]) => void
|
||||
customClassName?: string
|
||||
service?: ServiceDetailType
|
||||
serviceId?: string
|
||||
currentTab?: string
|
||||
openModal?: (type: 'apply') => void
|
||||
consumerParams?: ConsumerParamsType
|
||||
}
|
||||
export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, IntegrationAIContainerProps>(
|
||||
({
|
||||
@@ -69,8 +75,9 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
customClassName,
|
||||
service,
|
||||
serviceId,
|
||||
currentTab,
|
||||
openModal
|
||||
currentTab,
|
||||
openModal,
|
||||
consumerParams
|
||||
}: IntegrationAIContainerProps, ref) => {
|
||||
/** 当前激活的标签 */
|
||||
const [activeTab, setActiveTab] = useState(type === 'service' ? 'openApi' : 'mcp')
|
||||
@@ -180,7 +187,35 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
message.error(errorInfo?.toString() || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消费者 MCP 配置
|
||||
* @returns
|
||||
*/
|
||||
const getConsumerMcpConfig = () => {
|
||||
fetchData<BasicResponse<null>>('app/mcp/config', {
|
||||
method: 'GET',
|
||||
eoParams: { app: consumerParams?.consumerId, team: consumerParams?.teamId }
|
||||
})
|
||||
.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?.toString() || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -191,6 +226,10 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
navigator('/mcpKey')
|
||||
}
|
||||
|
||||
const dropAuthPage = () => {
|
||||
navigator(`/consumer/${consumerParams?.teamId}/inside/${consumerParams?.consumerId}/authorization`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局 API Key 列表
|
||||
*/
|
||||
@@ -217,7 +256,7 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
message.error(errorInfo?.toString() || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -229,12 +268,12 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
}))
|
||||
|
||||
/**
|
||||
* 获取服务 API Key 列表
|
||||
* 获取 API Key 列表
|
||||
*/
|
||||
const getServiceKeysList = () => {
|
||||
const getServiceKeysList = (consumerId?: string) => {
|
||||
fetchData<BasicResponse<null>>(`my/app/apikeys`, {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId }
|
||||
eoParams: consumerId ? { app: consumerId } : { service: serviceId }
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg, data } = response
|
||||
@@ -258,7 +297,7 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
message.error(errorInfo?.toString() || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -345,6 +384,10 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
getGlobalMcpConfig()
|
||||
setMcpServerUrl('mcp/global/sse')
|
||||
getGlobalKeysList()
|
||||
} else if (type === 'consumer'){
|
||||
getConsumerMcpConfig()
|
||||
setMcpServerUrl(`mcp/app/${consumerParams?.consumerId}/sse`)
|
||||
getServiceKeysList(consumerParams?.consumerId)
|
||||
} else {
|
||||
service?.basic.enableMcp && setMcpServerUrl(`mcp/service/${serviceId}/sse`)
|
||||
getServiceKeysList()
|
||||
@@ -362,6 +405,7 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
useEffect(() => {
|
||||
initTabsData()
|
||||
type === 'global' && getGlobalMcpConfig()
|
||||
type === 'consumer' && getConsumerMcpConfig()
|
||||
}, [state.language])
|
||||
/**
|
||||
* 切换标签
|
||||
@@ -408,7 +452,7 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
}
|
||||
connectMcpServer()
|
||||
}
|
||||
}, [mcpServerUrl, ...(type === 'global' ? [state.language] : [])])
|
||||
}, [mcpServerUrl, ...(type === 'global' || type === 'consumer' ? [state.language] : [])])
|
||||
/**
|
||||
* 获取 MCP tools
|
||||
*/
|
||||
@@ -452,7 +496,7 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{type === 'service' && !apiKeyList.length ? (
|
||||
{(type === 'service' || type === 'consumer') && !apiKeyList.length ? (
|
||||
<>
|
||||
<Card
|
||||
style={{ borderRadius: '10px' }}
|
||||
@@ -461,12 +505,23 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
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>
|
||||
{
|
||||
type === 'service' ? (
|
||||
<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>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-3">
|
||||
<span className="text-[14px] mb-5">{$t('未配置 API Key')}</span>
|
||||
<Button type="primary" onClick={() => dropAuthPage()}>
|
||||
{$t('配置')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Card>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -14,7 +14,7 @@ export const MemberDropdownModal = forwardRef<MemberDropdownModalHandle,MemberDr
|
||||
const {fetchData} = useFetch()
|
||||
const [departmentList, setDepartmentList] = useState<DepartmentListItem[]>([])
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
const [disableEditMemberData] = useState<boolean>(entity?.from === 'feishu')
|
||||
const save:()=>Promise<boolean | string> = ()=>{
|
||||
let url:string
|
||||
let method:string
|
||||
@@ -182,27 +182,28 @@ export const MemberDropdownModal = forwardRef<MemberDropdownModalHandle,MemberDr
|
||||
name="name"
|
||||
rules={[{required: true,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" disabled={type ==='editMember'} placeholder={$t(PLACEHOLDER.input)}/>
|
||||
<Input className="w-INPUT_NORMAL" disabled={disableEditMemberData || type ==='editMember'} placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
<Form.Item<MemberDropdownModalFieldType>
|
||||
label={$t("邮箱")}
|
||||
name="email"
|
||||
rules={[{required: true,whitespace:true },{type:"email",message: $t(VALIDATE_MESSAGE.email)}]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
<Input className="w-INPUT_NORMAL" disabled={disableEditMemberData} placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
<Form.Item<MemberDropdownModalFieldType>
|
||||
label={$t("密码")}
|
||||
name="password"
|
||||
rules={[{required: type === 'addMember',whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
<Input disabled={disableEditMemberData} className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
<Form.Item<MemberDropdownModalFieldType>
|
||||
label={$t("部门")}
|
||||
name="departmentIds"
|
||||
>
|
||||
<TreeSelect
|
||||
disabled={disableEditMemberData}
|
||||
className="w-INPUT_NORMAL"
|
||||
fieldNames={{label:'name',value:'id',children:'children'}}
|
||||
showSearch
|
||||
|
||||
@@ -95,14 +95,15 @@ const AddToDepartment = forwardRef<AddToDepartmentHandle, AddToDepartmentProps>(
|
||||
treeData?.map((x: DataNode) => ({
|
||||
...x,
|
||||
name: $t((x as unknown as { name: string }).name),
|
||||
checkable: false,
|
||||
children: x.children?.map(y => ({ ...y, checkable: false }))
|
||||
checkable: false, // 根节点不可选中
|
||||
children: x.children?.map(y => ({ ...y, checkable: true })) // 子节点可以选中
|
||||
})),
|
||||
[state.language, treeData]
|
||||
)
|
||||
|
||||
const onCheck: TreeProps['onCheck'] = (checkedKeys: string[]) => {
|
||||
setSelectedKeys(checkedKeys.checked)
|
||||
const onCheck: TreeProps['onCheck'] = (checkedKeys, info) => {
|
||||
const selectedIds = Array.isArray(checkedKeys) ? checkedKeys : checkedKeys.checked || []
|
||||
setSelectedKeys(selectedIds)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -153,11 +154,12 @@ const MemberList = () => {
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true)
|
||||
const [tableListDataSource, setTableListDataSource] = useState<MemberTableListItem[]>([])
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
const { topGroupId, selectedDepartmentIds, refreshGroup } = useOutletContext<{
|
||||
const { topGroupId, selectedDepartmentIds, refreshGroup, refreshTableCount } = useOutletContext<{
|
||||
topGroupId: string
|
||||
departmentList: DepartmentListItem[]
|
||||
selectedDepartmentIds: string[]
|
||||
refreshGroup: () => void
|
||||
refreshTableCount: number
|
||||
}>()
|
||||
const AddMemberRef = useRef<MemberDropdownModalHandle>(null)
|
||||
const EditMemberRef = useRef<MemberDropdownModalHandle>(null)
|
||||
@@ -396,7 +398,7 @@ const MemberList = () => {
|
||||
width: 600,
|
||||
okText: $t('确认'),
|
||||
okButtonProps: {
|
||||
disabled: isActionAllowed(type)
|
||||
disabled: isActionAllowed(type) || (type === 'editMember' && entity?.from === 'feishu')
|
||||
},
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
@@ -415,6 +417,13 @@ const MemberList = () => {
|
||||
getDepartmentList()
|
||||
}, [])
|
||||
|
||||
// 监听外部刷新触发器
|
||||
useEffect(() => {
|
||||
if (refreshTableCount > 0) {
|
||||
manualReloadTable()
|
||||
}
|
||||
}, [refreshTableCount])
|
||||
|
||||
const getDepartmentList = async () => {
|
||||
setDepartmentValueEnum([])
|
||||
const { code, data, msg } = await fetchData<BasicResponse<{ department: DepartmentListItem }>>(
|
||||
|
||||
@@ -36,6 +36,11 @@ const MemberPage = ()=>{
|
||||
const [selectedDepartmentId, setSelectedDepartmentId] = useState<string>('-1')
|
||||
const {accessData,state} = useGlobalContext()
|
||||
const [refreshMemberCount, setRefreshMemberCount] = useState<number>(0)
|
||||
const [refreshTableCount, setRefreshTableCount] = useState<number>(0)
|
||||
|
||||
const refreshMemberTable = () => {
|
||||
setRefreshTableCount(prev => prev + 1)
|
||||
}
|
||||
const onSearchWordChange = (e:string)=>{
|
||||
setSearchWord(e || '')
|
||||
}
|
||||
@@ -90,7 +95,7 @@ const MemberPage = ()=>{
|
||||
case 'addChild':
|
||||
return AddChildRef.current?.save().then((res)=>{if(res === true)getDepartmentList()})
|
||||
case 'addMember':
|
||||
return AddMemberRef.current?.save().then((res)=>{if(res === true){getDepartmentList();setRefreshMemberCount(pre=>pre+1)}})
|
||||
return AddMemberRef.current?.save().then((res)=>{if(res === true){getDepartmentList();setRefreshMemberCount(pre=>pre+1);refreshMemberTable()}})
|
||||
case 'rename':
|
||||
return RenameRef.current?.save().then((res)=>{if(res === true)getDepartmentList()})
|
||||
case 'delete':
|
||||
@@ -262,7 +267,7 @@ const MemberPage = ()=>{
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 p-btnbase pr-PAGE_INSIDE_X overflow-x-hidden">
|
||||
<Outlet context={{refreshMemberCount, selectedDepartmentIds,refreshGroup:()=>getDepartmentList()}}/>
|
||||
<Outlet context={{refreshMemberCount, selectedDepartmentIds,refreshGroup:()=>getDepartmentList(), refreshTableCount}}/>
|
||||
</div>
|
||||
</div>
|
||||
</InsidePage>);
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import EditableTable from "@common/components/aoplatform/EditableTable"
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission"
|
||||
import { BasicResponse, PLACEHOLDER, STATUS_CODE } from "@common/const/const"
|
||||
import { useFetch } from "@common/hooks/http"
|
||||
import { $t } from "@common/locales"
|
||||
import { PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS, PartitionDataLogConfigFieldType, PartitionDataLogHeaderListFieldType } from "@core/const/partitions/types"
|
||||
import { Button, Form, Input, message } from "antd"
|
||||
import { useEffect } from "react"
|
||||
import EditableTable from '@common/components/aoplatform/EditableTable'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { BasicResponse, PLACEHOLDER, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { $t } from '@common/locales'
|
||||
import {
|
||||
PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS,
|
||||
PartitionDataLogConfigFieldType,
|
||||
PartitionDataLogHeaderListFieldType
|
||||
} from '@core/const/partitions/types'
|
||||
import { Button, Form, Input, message, Select } from 'antd'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export type DashboardPageShowStatus = 'view' | 'edit'
|
||||
export type DashboardSettingEditProps = {
|
||||
@@ -15,7 +19,7 @@ export type DashboardSettingEditProps = {
|
||||
}
|
||||
const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
|
||||
const { changeStatus, refreshData, data } = props
|
||||
const [form] = Form.useForm();
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
const onFinish = () => {
|
||||
@@ -23,10 +27,16 @@ const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
|
||||
const formData = {
|
||||
config: {
|
||||
url: value.url,
|
||||
headers: value.headers.filter((item: PartitionDataLogHeaderListFieldType) => item.key).map((item: PartitionDataLogHeaderListFieldType) => ({key:item.key, value:item.value || ''}))
|
||||
headers: value.headers
|
||||
.filter((item: PartitionDataLogHeaderListFieldType) => item.key)
|
||||
.map((item: PartitionDataLogHeaderListFieldType) => ({ key: item.key, value: item.value || '' }))
|
||||
}
|
||||
}
|
||||
fetchData<BasicResponse<{ info: PartitionDataLogConfigFieldType }>>('log/loki', { method: 'POST', body: JSON.stringify(formData), eoParams: {} }).then(response => {
|
||||
fetchData<BasicResponse<{ info: PartitionDataLogConfigFieldType }>>('log/loki', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(formData),
|
||||
eoParams: {}
|
||||
}).then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t('操作成功,即将刷新页面'))
|
||||
@@ -38,15 +48,26 @@ const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => { form.setFieldsValue(data) }, [data])
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
...data,
|
||||
headers: data?.headers?.length ? data.headers : [
|
||||
{
|
||||
key: '',
|
||||
value: ''
|
||||
}
|
||||
],
|
||||
driver: 'loki'
|
||||
})
|
||||
}, [data])
|
||||
|
||||
useEffect(() => {
|
||||
return (form.setFieldsValue({}))
|
||||
}, []);
|
||||
return form.setFieldsValue({})
|
||||
}, [])
|
||||
return (
|
||||
<>
|
||||
<div className="overflow-auto h-full">
|
||||
<WithPermission access={''} >
|
||||
<WithPermission access={''}>
|
||||
<Form
|
||||
form={form}
|
||||
className="mx-auto flex flex-col justify-between h-full"
|
||||
@@ -55,23 +76,29 @@ const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item<PartitionDataLogConfigFieldType>
|
||||
label={$t("请求前缀")}
|
||||
name="url"
|
||||
label={$t('数据源类型')}
|
||||
name="driver"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={[{ label: 'Loki', value: 'loki' }]}
|
||||
></Select>
|
||||
</Form.Item>
|
||||
<Form.Item<PartitionDataLogConfigFieldType> label={$t('请求前缀')} name="url" rules={[{ required: true }]}>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<PartitionDataLogConfigFieldType>
|
||||
label={$t("HTTP 头部")}
|
||||
name="headers"
|
||||
>
|
||||
<Form.Item<PartitionDataLogConfigFieldType> label={$t('HTTP 头部')} name="headers">
|
||||
<EditableTable<PartitionDataLogConfigFieldType & { _id: string }>
|
||||
configFields={PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="flex gap-btnbase">
|
||||
<WithPermission access='system.devops.data_source.edit'>
|
||||
<WithPermission access="system.devops.data_source.edit">
|
||||
<Button type="primary" htmlType="submit">
|
||||
{$t('保存')}
|
||||
</Button>
|
||||
@@ -84,7 +111,7 @@ const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
|
||||
</WithPermission>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default DataLogSettingEdit;
|
||||
export default DataLogSettingEdit
|
||||
|
||||
@@ -161,7 +161,7 @@ const PartitionInsideDashboardSetting: FC = () => {
|
||||
className="overflow-hidden mt-[30px] w-full max-h-full flex flex-col justify-between"
|
||||
title={
|
||||
<div>
|
||||
<span className="text-MAIN_TEXT my-btnybase mr-btnbase"> {$t('数据日志')}</span>
|
||||
<span className="text-MAIN_TEXT my-btnybase mr-btnbase"> {$t('请求日志')}</span>
|
||||
{!dataLogLoading && !dataLogData && <Tag color="#f50">{$t('未配置')}</Tag>}
|
||||
</div>
|
||||
}
|
||||
@@ -220,6 +220,11 @@ export function DataLogConfigPreview(x: PartitionDataLogConfigFieldType) {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-[4px] ">
|
||||
<Row className="">
|
||||
<Col className="font-bold text-right pr-[4px]">{$t('数据源')}:</Col>
|
||||
{/* 先写死,或许会有选择列表,但现在可以不用 */}
|
||||
<Col>Loki</Col>
|
||||
</Row>
|
||||
<Row className="">
|
||||
<Col className="font-bold text-right pr-[4px]">{$t('请求前缀')}:</Col>
|
||||
<Col>{x?.url}</Col>
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
|
||||
import { useEffect } from 'react'
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
export default function ServicePolicyLayout() {
|
||||
const location = useLocation()
|
||||
const pathName = location.pathname
|
||||
const navigator = useNavigate()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
useEffect(() => {
|
||||
const tmpPath = pathName.split('/')
|
||||
if (tmpPath[tmpPath.length - 1] === 'servicepolicy') {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('服务策略')
|
||||
}
|
||||
])
|
||||
navigator('datamasking/list')
|
||||
}
|
||||
}, [pathName])
|
||||
|
||||
@@ -80,19 +80,6 @@ const DataMaskingConfig = forwardRef<DataMaskingConfigHandle>((_,ref) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('服务策略'),
|
||||
onClick: () => navigator(serviceId ? `/service/${teamId}/aiInside/${serviceId}/servicepolicy` : '')
|
||||
},
|
||||
{
|
||||
title: policyId !== undefined ? $t('编辑服务策略') : $t('添加服务策略')
|
||||
}
|
||||
])
|
||||
if (policyId !== undefined) {
|
||||
setOnEdit(true);
|
||||
getPolicyInfo();
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import ServiceLogs from "./ServiceLogs"
|
||||
const AiServiceLogsContainer = () => {
|
||||
return <ServiceLogs serviceType="aiService" />
|
||||
}
|
||||
|
||||
export default AiServiceLogsContainer
|
||||
@@ -0,0 +1,89 @@
|
||||
import { IconButton } from '@common/components/postcat/api/IconButton'
|
||||
import useCopyToClipboard from '@common/hooks/copy'
|
||||
import { RESPONSE_TIPS } from '@common/const/const'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { App } from 'antd'
|
||||
import ReactJson from 'react-json-view'
|
||||
|
||||
const ApiNetWorkDataPreview = ({ configContent = {} }: { configContent?: { [key: string]: string | undefined } }) => {
|
||||
/** 复制组件 */
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
/** 弹窗组件 */
|
||||
const { message } = App.useApp()
|
||||
/**
|
||||
* 复制
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
const handleCopy = async (value: string): Promise<void> => {
|
||||
if (value) {
|
||||
copyToClipboard(value)
|
||||
message.success($t(RESPONSE_TIPS.copySuccess))
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 判断字符串是否是有效的JSON对象字符串
|
||||
*/
|
||||
const isJsonString = (str: string): boolean => {
|
||||
try {
|
||||
const parsed = JSON.parse(str)
|
||||
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.keys(configContent).filter((item) => !!configContent[item]).map((item) => {
|
||||
return (
|
||||
<div className="overflow-auto mb-[15px]">
|
||||
<div className="font-semibold text-[16px] mb-[10px]">{item}</div>
|
||||
<div className="bg-[#0a0b21] text-white p-4 rounded-md my-2 font-mono text-sm overflow-auto relative">
|
||||
{!configContent[item] ? (
|
||||
<pre className="whitespace-pre-wrap break-words"></pre>
|
||||
) : isJsonString(configContent[item] || '') ? (
|
||||
// 如果是有效的JSON对象字符串,使用ReactJson渲染
|
||||
<ReactJson
|
||||
src={JSON.parse(configContent[item] || '')}
|
||||
theme="monokai"
|
||||
indentWidth={2}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
name={false}
|
||||
collapsed={false}
|
||||
enableClipboard={false}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
// 如果是普通字符串,直接用pre渲染
|
||||
<pre className="whitespace-pre-wrap break-words my-[8px]">{configContent[item]}</pre>
|
||||
)}
|
||||
<IconButton
|
||||
name="copy"
|
||||
onClick={() => handleCopy(configContent[item] || '')}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '5px',
|
||||
right: '5px',
|
||||
color: '#999',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default ApiNetWorkDataPreview
|
||||
@@ -0,0 +1,362 @@
|
||||
import { Descriptions, DescriptionsProps, Spin, Tabs, Tooltip, message } from 'antd'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import React from 'react'
|
||||
import { ExclamationCircleOutlined, LoadingOutlined } from '@ant-design/icons'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import ApiNetWorkDataPreview from './ApiNetWorkDataPreview'
|
||||
import { LogItem } from './ServiceLogs'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
|
||||
// 定义状态码颜色映射枚举
|
||||
export enum HttpStatusColor {
|
||||
SUCCESS = '#7EC26A',
|
||||
CLIENT_ERROR = '#F2CF59',
|
||||
SERVER_ERROR = '#f80f34'
|
||||
}
|
||||
|
||||
type LogDetailProps = {
|
||||
selectedRow?: LogItem
|
||||
serviceType: 'aiService' | 'restService'
|
||||
serviceId?: string
|
||||
teamId?: string
|
||||
}
|
||||
|
||||
type AIServiceDetailType = {
|
||||
id: string
|
||||
api: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
logTime: string
|
||||
consumer: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
isSystemConsumer: boolean
|
||||
status: string
|
||||
provider: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
model: string
|
||||
ip: string
|
||||
request: {
|
||||
header: string
|
||||
body: string
|
||||
origin: string
|
||||
token: number
|
||||
}
|
||||
response: {
|
||||
header: string
|
||||
body: string
|
||||
origin: string
|
||||
token: string
|
||||
}
|
||||
}
|
||||
|
||||
type RestServiceDetailType = {
|
||||
id: string
|
||||
api: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
logTime: string
|
||||
consumer: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
isSystemConsumer: boolean
|
||||
status: string
|
||||
ip: string
|
||||
request: {
|
||||
header: string
|
||||
origin: string
|
||||
}
|
||||
response: {
|
||||
header: string
|
||||
origin: string
|
||||
}
|
||||
}
|
||||
|
||||
const LogDetail = ({ selectedRow, serviceType, serviceId, teamId }: LogDetailProps) => {
|
||||
/** 顶部描述 */
|
||||
const [descriptionItems, setDescriptionItems] = useState<DescriptionsProps['items']>()
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
/** Request 标签页数据 */
|
||||
const [requestInfoData, setRequestInfoData] = useState<{ [key: string]: string | undefined }>()
|
||||
/** Response 标签页数据 */
|
||||
const [responseInfoData, setResponseInfoData] = useState<{ [key: string]: string | undefined }>()
|
||||
/** 面板 loading */
|
||||
const [dashboardLoading, setDashboardLoading] = useState(true)
|
||||
/**
|
||||
* 请求数据
|
||||
*/
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
/**
|
||||
* 根据状态码返回对应颜色的文本
|
||||
* @param status 状态
|
||||
* @returns
|
||||
*/
|
||||
const renderStatusWithColor = (status: string) => {
|
||||
// 获取状态码首位数字
|
||||
const firstDigit = String(status).charAt(0)
|
||||
let color = ''
|
||||
switch (firstDigit) {
|
||||
case '2':
|
||||
color = HttpStatusColor.SUCCESS
|
||||
break
|
||||
case '4':
|
||||
color = HttpStatusColor.CLIENT_ERROR
|
||||
break
|
||||
case '5':
|
||||
color = HttpStatusColor.SERVER_ERROR
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return color ? <span style={{ color }}>{status}</span> : status
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签页内容
|
||||
*/
|
||||
const tabItems = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: 'request',
|
||||
label: 'Request',
|
||||
children: <ApiNetWorkDataPreview configContent={requestInfoData} />
|
||||
},
|
||||
{
|
||||
key: 'response',
|
||||
label: 'Response',
|
||||
children: <ApiNetWorkDataPreview configContent={responseInfoData} />
|
||||
}
|
||||
],
|
||||
[state.language, requestInfoData, responseInfoData]
|
||||
)
|
||||
|
||||
/**
|
||||
* 设置 AI 描述文案
|
||||
*/
|
||||
const getAIServiceDescriptionItemsList = ({
|
||||
time,
|
||||
api,
|
||||
consumer,
|
||||
status,
|
||||
model,
|
||||
ip
|
||||
}: {
|
||||
time: string
|
||||
api: string
|
||||
consumer: string
|
||||
status: string
|
||||
model: string
|
||||
ip: string
|
||||
}) => {
|
||||
setDescriptionItems([
|
||||
{
|
||||
key: 'time',
|
||||
label: $t('时间戳'),
|
||||
children: time
|
||||
},
|
||||
{
|
||||
key: 'api',
|
||||
label: $t('API / Tools'),
|
||||
children: api
|
||||
},
|
||||
{
|
||||
key: 'consumer',
|
||||
label: $t('消费者'),
|
||||
children: consumer
|
||||
},
|
||||
{
|
||||
key: 'httpStatus',
|
||||
label: $t('HTTP 状态'),
|
||||
children: renderStatusWithColor(status)
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
label: $t('模型'),
|
||||
children: model
|
||||
},
|
||||
{
|
||||
key: 'ip',
|
||||
label: $t('IP'),
|
||||
children: ip
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 REST 描述文案
|
||||
*/
|
||||
const getRestServiceDescriptionItemsList = ({
|
||||
time,
|
||||
api,
|
||||
consumer,
|
||||
isSystemConsumer,
|
||||
status,
|
||||
ip
|
||||
}: {
|
||||
time: string
|
||||
api: string
|
||||
consumer: string
|
||||
isSystemConsumer?: boolean
|
||||
status: string
|
||||
ip: string
|
||||
}) => {
|
||||
setDescriptionItems([
|
||||
{
|
||||
key: 'time',
|
||||
label: $t('时间戳'),
|
||||
children: time
|
||||
},
|
||||
{
|
||||
key: 'api',
|
||||
label: $t('API / Tools'),
|
||||
children: api
|
||||
},
|
||||
{
|
||||
key: 'consumer',
|
||||
label: $t('消费者'),
|
||||
children: (
|
||||
<>
|
||||
<span className="mr-[50px]">{consumer}</span>
|
||||
{isSystemConsumer && (
|
||||
<span>
|
||||
<span>System-level API Key</span>
|
||||
<Tooltip title={$t('通过系统级别的 API Key 来调用')}>
|
||||
<span className="ml-[12px] items-center">
|
||||
<ExclamationCircleOutlined className="text-[14px] h-[14px] w-[14px]" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'httpStatus',
|
||||
label: $t('HTTP 状态'),
|
||||
children: renderStatusWithColor(status)
|
||||
},
|
||||
{
|
||||
key: 'ip',
|
||||
label: $t('IP'),
|
||||
children: ip
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AI 服务日志详情
|
||||
*/
|
||||
const getAIServiceLogDetail = () => {
|
||||
fetchData<BasicResponse<{ log: AIServiceDetailType }>>('service/log/ai', {
|
||||
method: 'GET',
|
||||
eoParams: { log: selectedRow?.id, service: serviceId, team: teamId },
|
||||
eoTransformKeys: ['is_system_consumer', 'log_time']
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const result = data.log
|
||||
getAIServiceDescriptionItemsList({
|
||||
time: result.logTime,
|
||||
api: result.api.name,
|
||||
consumer: result.consumer.name,
|
||||
status: result.status,
|
||||
model: result.model,
|
||||
ip: result.ip
|
||||
})
|
||||
setRequestInfoData({
|
||||
Header: result.request.header,
|
||||
Body: result.request.body
|
||||
})
|
||||
setResponseInfoData({
|
||||
Header: result.response.header,
|
||||
Body: result.response.body
|
||||
})
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
setDashboardLoading(false)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 获取 REST 服务日志详情
|
||||
*/
|
||||
const getRestServiceLogDetail = () => {
|
||||
fetchData<BasicResponse<{ log: RestServiceDetailType }>>('service/log/rest', {
|
||||
method: 'GET',
|
||||
eoParams: { log: selectedRow?.id, service: serviceId, team: teamId },
|
||||
eoTransformKeys: ['is_system_consumer', 'log_time']
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const result = data.log
|
||||
getRestServiceDescriptionItemsList({
|
||||
time: result.logTime,
|
||||
api: result.api.name,
|
||||
consumer: result.consumer.name,
|
||||
status: result.status,
|
||||
ip: result.ip,
|
||||
isSystemConsumer: result.isSystemConsumer
|
||||
})
|
||||
setRequestInfoData({
|
||||
Header: result.request.header,
|
||||
Body: result.request.body
|
||||
})
|
||||
setResponseInfoData({
|
||||
Header: result.response.header,
|
||||
Body: result.response.body
|
||||
})
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
setDashboardLoading(false)
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
setDashboardLoading(true)
|
||||
serviceType === 'aiService' ? getAIServiceLogDetail() : getRestServiceLogDetail()
|
||||
}, [serviceType])
|
||||
|
||||
return (
|
||||
<Spin
|
||||
className="h-full pb-[20px]"
|
||||
wrapperClassName="h-full min-h-[150px]"
|
||||
indicator={
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ transform: 'scale(1.5)' }}>
|
||||
<LoadingOutlined style={{ fontSize: 30 }} spin />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
spinning={dashboardLoading}
|
||||
>
|
||||
<Descriptions
|
||||
column={1}
|
||||
className="[&_.ant-descriptions-item]:p-0 [&_.ant-descriptions-item]:py-[5px]"
|
||||
colon={false}
|
||||
items={descriptionItems}
|
||||
classNames={{
|
||||
label: 'w-[250px] text-right pr-[12px]'
|
||||
}}
|
||||
contentStyle={{ fontWeight: '600' }}
|
||||
/>
|
||||
<div className="mt-[5px] service-log-tab">
|
||||
<Tabs
|
||||
className="overflow-hidden h-full [&>.ant-tabs-content-holder]:overflow-auto global-policy-tabs"
|
||||
items={tabItems}
|
||||
/>
|
||||
</div>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
|
||||
export default LogDetail
|
||||
@@ -0,0 +1,7 @@
|
||||
import ServiceLogs from "./ServiceLogs"
|
||||
|
||||
const RestServiceLogsContainer = () => {
|
||||
return <ServiceLogs serviceType="restService" />
|
||||
}
|
||||
|
||||
export default RestServiceLogsContainer
|
||||
@@ -0,0 +1,263 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { Drawer, Spin, message } from 'antd'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import DateSelectFilter, { TimeOption } from '../serviceOverview/filter/DateSelectFilter'
|
||||
import { TimeRange } from '@common/components/aoplatform/TimeRangeSelector'
|
||||
import PageList from '@common/components/aoplatform/PageList'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { REST_SERVICE_LOG_LIST, AI_SERVICE_LOG_LIST } from '@core/const/system/const'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import LogDetail, { HttpStatusColor } from './LogDetail'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { ActionType, ParamsType } from '@ant-design/pro-components'
|
||||
import { getTime } from '@dashboard/utils/dashboard'
|
||||
|
||||
export type LogItem = {
|
||||
id: string
|
||||
api: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
status: number
|
||||
logTime: string
|
||||
responseTime: string
|
||||
token?: number
|
||||
model?: string
|
||||
tokenPerSecond?: string
|
||||
traffic?: string
|
||||
consumers?: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
provider?: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
const ServiceLogs = ({ serviceType }: { serviceType: 'aiService' | 'restService' }) => {
|
||||
/** 路由参数 */
|
||||
const { serviceId, teamId } = useParams<{ serviceId: string; teamId: string }>()
|
||||
/** 面板 loading */
|
||||
const [dashboardLoading, setDashboardLoading] = useState(true)
|
||||
/** 当前选中的时间范围 */
|
||||
const [timeRange, setTimeRange] = useState<TimeRange | undefined>()
|
||||
/** 默认时间 */
|
||||
const [defaultTime] = useState<TimeOption>('day')
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
/**
|
||||
* 请求数据
|
||||
*/
|
||||
const { fetchData } = useFetch()
|
||||
// 打开侧边弹窗
|
||||
const [drawerOpen, setDrawerOpen] = useState<boolean>(false)
|
||||
/** 选中的行 */
|
||||
const [selectedRow, setSelectedRow] = useState<LogItem>()
|
||||
/**
|
||||
* 列表ref
|
||||
*/
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
/** 列 */
|
||||
const columns = useMemo(() => {
|
||||
return [...(serviceType === 'aiService' ? AI_SERVICE_LOG_LIST : REST_SERVICE_LOG_LIST)].map((x) => {
|
||||
if (x.dataIndex === 'status') {
|
||||
x.render = (text: any, record: any) => (
|
||||
<>
|
||||
<div className="w-full">{renderStatusWithColor(record.status)}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return {
|
||||
...x,
|
||||
title: typeof x.title === 'string' ? $t(x.title as string) : x.title
|
||||
}
|
||||
})
|
||||
}, [state.language])
|
||||
|
||||
/**
|
||||
* 根据状态码返回对应颜色的文本
|
||||
* @param status 状态
|
||||
* @returns
|
||||
*/
|
||||
const renderStatusWithColor = (status: string | number) => {
|
||||
// 获取状态码首位数字
|
||||
const firstDigit = status.toString().charAt(0)
|
||||
let color = ''
|
||||
switch (firstDigit) {
|
||||
case '2':
|
||||
color = HttpStatusColor.SUCCESS
|
||||
break
|
||||
case '4':
|
||||
color = HttpStatusColor.CLIENT_ERROR
|
||||
break
|
||||
case '5':
|
||||
color = HttpStatusColor.SERVER_ERROR
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return color ? <span style={{ color }}>{status}</span> : status
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AI 列表数据
|
||||
* @param dataType
|
||||
* @returns
|
||||
*/
|
||||
const getAiServiceLogList = (
|
||||
params: ParamsType & {
|
||||
pageSize?: number | undefined
|
||||
current?: number | undefined
|
||||
keyword?: string | undefined
|
||||
}
|
||||
) => {
|
||||
return fetchData<BasicResponse<{ log: LogItem[] }>>(`service/logs/ai`, {
|
||||
method: 'GET',
|
||||
eoParams: {
|
||||
service: serviceId,
|
||||
team: teamId,
|
||||
start: timeRange?.start,
|
||||
end: timeRange?.end,
|
||||
page: params?.current,
|
||||
page_size:params?.pageSize
|
||||
},
|
||||
eoTransformKeys: ['log_time', 'response_time', 'token_per_second']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 保存数据
|
||||
return {
|
||||
data: data.logs,
|
||||
total: data.total,
|
||||
success: true
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return { data: [], success: false }
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
return { data: [], success: false }
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 获取 REST 列表数据
|
||||
* @param dataType
|
||||
* @returns
|
||||
*/
|
||||
const getRestServiceLogList = (
|
||||
params: ParamsType & {
|
||||
pageSize?: number | undefined
|
||||
current?: number | undefined
|
||||
keyword?: string | undefined
|
||||
}
|
||||
) => {
|
||||
console.log('params===', params)
|
||||
return fetchData<BasicResponse<{ log: LogItem[] }>>(`service/logs/rest`, {
|
||||
method: 'GET',
|
||||
eoParams: {
|
||||
service: serviceId,
|
||||
team: teamId,
|
||||
start: timeRange?.start,
|
||||
end: timeRange?.end,
|
||||
page: params?.current,
|
||||
page_size:params?.pageSize
|
||||
},
|
||||
eoTransformKeys: ['log_time', 'response_time', 'token_per_second']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 保存数据
|
||||
return {
|
||||
data: data.logs,
|
||||
total: data.total,
|
||||
success: true
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return { data: [], success: false }
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
return { data: [], success: false }
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const { startTime, endTime } = getTime(defaultTime, [])
|
||||
setTimeRange({
|
||||
start: startTime,
|
||||
end: endTime
|
||||
})
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
if (timeRange) {
|
||||
pageListRef.current?.reload()
|
||||
}
|
||||
}, [timeRange])
|
||||
|
||||
/** 行点击 */
|
||||
const handleRowClick = (record: LogItem) => {
|
||||
setSelectedRow(record)
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
|
||||
/** 时间选择回调 */
|
||||
const selectCallback = (date: TimeRange) => {
|
||||
setTimeRange(date)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setDashboardLoading(false)
|
||||
}, [])
|
||||
return (
|
||||
<Spin
|
||||
className="h-full pb-[20px]"
|
||||
wrapperClassName="h-full min-h-[150px] overflow-hidden"
|
||||
indicator={
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ transform: 'scale(1.5)' }}>
|
||||
<LoadingOutlined style={{ fontSize: 30 }} spin />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
spinning={dashboardLoading}
|
||||
>
|
||||
<div className="h-full mr-PAGE_INSIDE_X">
|
||||
<DateSelectFilter selectCallback={selectCallback} customClassNames={'pt-[0px]'} defaultTime={defaultTime} />
|
||||
<div style={{ height: 'calc(100% - -3px)' }} className="mt-[20px] overflow-hidden">
|
||||
<PageList
|
||||
ref={pageListRef}
|
||||
id={`${serviceType}_logs`}
|
||||
columns={[...columns]}
|
||||
request={async (
|
||||
params: ParamsType & {
|
||||
pageSize?: number | undefined
|
||||
current?: number | undefined
|
||||
keyword?: string | undefined
|
||||
}
|
||||
) => (serviceType === 'aiService' ? getAiServiceLogList(params) : getRestServiceLogList(params))}
|
||||
onRowClick={(row: LogItem) => handleRowClick(row)}
|
||||
/>
|
||||
</div>
|
||||
<Drawer
|
||||
destroyOnClose={true}
|
||||
maskClosable={false}
|
||||
title={$t('日志详情')}
|
||||
width={'40%'}
|
||||
onClose={() => setDrawerOpen(false)}
|
||||
open={drawerOpen}
|
||||
>
|
||||
<LogDetail selectedRow={selectedRow} serviceId={serviceId} teamId={teamId} serviceType={serviceType} />
|
||||
</Drawer>
|
||||
</div>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceLogs
|
||||
@@ -0,0 +1,7 @@
|
||||
import ServiceOverview from "./serviceOverview"
|
||||
|
||||
const AiServiceContainer = () => {
|
||||
return <ServiceOverview serviceType="aiService" />
|
||||
}
|
||||
|
||||
export default AiServiceContainer
|
||||
@@ -0,0 +1,12 @@
|
||||
import { FC } from 'react'
|
||||
import ServiceOverview from './serviceOverview'
|
||||
|
||||
const RestServiceContainer: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<ServiceOverview serviceType="restService" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default RestServiceContainer
|
||||
@@ -0,0 +1,278 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import ECharts, { EChartsOption } from 'echarts-for-react'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
type AreaChartInfo = {
|
||||
title: string
|
||||
value: string
|
||||
date: string[]
|
||||
data: number[]
|
||||
max: string
|
||||
min: string
|
||||
originValue?: number
|
||||
showXAxis?: boolean
|
||||
}
|
||||
|
||||
type ServiceAreaCharProps = {
|
||||
customClassNames?: string
|
||||
dataInfo?: AreaChartInfo
|
||||
height?: number
|
||||
showAvgLine?: boolean
|
||||
customMarkLineValue?: number
|
||||
}
|
||||
|
||||
const ServiceAreaChart = ({ customClassNames, dataInfo, height, showAvgLine, customMarkLineValue }: ServiceAreaCharProps) => {
|
||||
const chartRef = useRef<ECharts>(null)
|
||||
const [option, setOption] = useState<EChartsOption | undefined>({})
|
||||
const [hasData, setHasData] = useState(true)
|
||||
const setChartOption = (dataInfo: AreaChartInfo) => {
|
||||
const dataExists = dataInfo.data && dataInfo.data.length > 0
|
||||
// 更新hasData状态
|
||||
setHasData(dataExists)
|
||||
const option = {
|
||||
tooltip: dataExists ? {
|
||||
trigger: 'axis',
|
||||
formatter: function (value: any) {
|
||||
// 如果是数组,取第一个参数的name
|
||||
const param = Array.isArray(value) ? value[0] : value
|
||||
let tooltipContent = `<div style="min-width:140px;padding:8px;">`
|
||||
const marker = `<span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${param.color};"></span>`
|
||||
tooltipContent += `<div style="margin-top: 8px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="margin-right: 4px;">${marker}</div>${param.name} <div style="font-weight:bold; margin-left: 20px;">${param.value}</div>
|
||||
</div>`
|
||||
tooltipContent += '</div>'
|
||||
return tooltipContent
|
||||
}
|
||||
} : {
|
||||
show: false // 没有数据时不显示tooltip
|
||||
},
|
||||
title: [
|
||||
{
|
||||
text: '{titleStyle|' + $t(dataInfo.title) + '}\n\n{valueStyle|' + dataInfo.value + '}',
|
||||
left: '2%',
|
||||
top: '0',
|
||||
textStyle: {
|
||||
rich: {
|
||||
titleStyle: {
|
||||
fontSize: 14,
|
||||
color: '#999999',
|
||||
fontWeight: 'normal',
|
||||
lineHeight: 20
|
||||
},
|
||||
valueStyle: {
|
||||
fontSize: 32,
|
||||
color: '#101010',
|
||||
fontWeight: 500,
|
||||
lineHeight: 40
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
toolbox: {
|
||||
show: false
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '3%',
|
||||
bottom: '0%',
|
||||
top: '110px',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: dataInfo.date,
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#ccc'
|
||||
}
|
||||
},
|
||||
show: false
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
boundaryGap: [0, '5%'],
|
||||
show: dataExists, // 没有数据时不显示Y轴
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
// 添加数据缩放组件,实现鼠标放大缩小,后续可能需要
|
||||
// dataZoom: [
|
||||
// {
|
||||
// type: 'inside', // 内置的数据区域缩放组件(使用鼠标滚轮缩放)
|
||||
// xAxisIndex: 0, // 设置缩放作用在第一个x轴
|
||||
// filterMode: 'filter',
|
||||
// start: 0,
|
||||
// end: 100
|
||||
// },
|
||||
// {
|
||||
// type: 'slider', // 滑动条型数据区域缩放组件
|
||||
// xAxisIndex: 0,
|
||||
// filterMode: 'filter',
|
||||
// height: 20,
|
||||
// bottom: 0,
|
||||
// start: 0,
|
||||
// end: 100,
|
||||
// handleIcon:
|
||||
// 'path://M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
|
||||
// handleSize: '80%',
|
||||
// handleStyle: {
|
||||
// color: '#fff',
|
||||
// shadowBlur: 3,
|
||||
// shadowColor: 'rgba(0, 0, 0, 0.6)',
|
||||
// shadowOffsetX: 2,
|
||||
// shadowOffsetY: 2
|
||||
// },
|
||||
// show: false // 默认隐藏底部的滑动条,可以改为 true 显示
|
||||
// }
|
||||
// ],
|
||||
// 添加空状态提示
|
||||
silent: !dataExists,
|
||||
graphic: !dataExists
|
||||
? [
|
||||
{
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'middle',
|
||||
style: {
|
||||
text: $t('暂无数据'),
|
||||
fontSize: 14,
|
||||
fill: '#999'
|
||||
}
|
||||
}
|
||||
]
|
||||
: [],
|
||||
series: [
|
||||
{
|
||||
name: dataInfo.title,
|
||||
type: 'line',
|
||||
symbol: 'none',
|
||||
sampling: 'lttb',
|
||||
itemStyle: {
|
||||
color: 'rgb(255, 70, 131)'
|
||||
},
|
||||
markLine: showAvgLine ? {
|
||||
silent: false,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
type: 'dashed'
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'insideEndTop',
|
||||
formatter: '{c}',
|
||||
color: '#000',
|
||||
fontSize: 10,
|
||||
backgroundColor: 'transparent',
|
||||
padding: [10, 4],
|
||||
borderRadius: 2,
|
||||
distance: -5
|
||||
},
|
||||
emphasis: {
|
||||
lineStyle: {
|
||||
width: 1 // 保持线条宽度不变,禁用默认的悬停加粗
|
||||
},
|
||||
label: {
|
||||
show: false // 悬停时不显示标签
|
||||
}
|
||||
},
|
||||
data: dataInfo?.originValue !== undefined ?
|
||||
[{ yAxis: dataInfo?.originValue, name: '自定义值' }] :
|
||||
[{ type: 'average', name: 'Avg' }]
|
||||
} : undefined,
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgb(255, 158, 68)'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgb(255, 70, 131)'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
data: dataInfo.data
|
||||
}
|
||||
]
|
||||
}
|
||||
const echartsInstance = chartRef.current?.getEchartsInstance()
|
||||
if (echartsInstance) {
|
||||
echartsInstance.setOption(option)
|
||||
}
|
||||
}
|
||||
// 使用深度监听来确保图表数据更新
|
||||
useEffect(() => {
|
||||
if (!dataInfo) return
|
||||
|
||||
// 直接获取 ECharts 实例并设置选项
|
||||
const echartsInstance = chartRef.current?.getEchartsInstance()
|
||||
if (echartsInstance) {
|
||||
// 清除已有的图表
|
||||
echartsInstance.clear()
|
||||
// 重新设置选项
|
||||
setChartOption(dataInfo)
|
||||
}
|
||||
}, [dataInfo, JSON.stringify(dataInfo)])
|
||||
|
||||
// 添加窗口大小变化监听,实现自适应
|
||||
useEffect(() => {
|
||||
// 定义resize处理函数
|
||||
const handleResize = () => {
|
||||
const echartsInstance = chartRef.current?.getEchartsInstance()
|
||||
if (echartsInstance) {
|
||||
echartsInstance.resize()
|
||||
}
|
||||
}
|
||||
|
||||
// 添加监听
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
// 组件卸载时移除监听
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<div className={`w-full ${customClassNames}`}>
|
||||
<div className="absolute top-[10px] left-[10px] w-full">
|
||||
<div className="relative top-[5px]">
|
||||
<div className="absolute top-[23px] right-[5%] grid grid-cols-[auto_auto] justify-items-end">
|
||||
<div className="flex justify-center items-center">
|
||||
<span className="text-[#FE564D] text-[9px]">▲</span>
|
||||
</div>
|
||||
<span className="ml-1 text-right">{dataInfo?.max}</span>
|
||||
<div className="flex justify-center items-center">
|
||||
<span className="text-[#27B148] text-[9px]">▼</span>
|
||||
</div>
|
||||
<span className="ml-1 text-right">{dataInfo?.min}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={!hasData ? { cursor: 'default', pointerEvents: 'none' } : {}}>
|
||||
<ECharts ref={chartRef} option={option} theme="apipark" style={{ height: height || 400 }} opts={{ renderer: 'svg' }} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceAreaChart
|
||||
@@ -0,0 +1,395 @@
|
||||
import ECharts, { EChartsOption } from 'echarts-for-react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { chartColors, defaultColor } from '@common/const/charts/theme'
|
||||
|
||||
export type BarChartInfo = {
|
||||
title: string
|
||||
value: string
|
||||
date: string[]
|
||||
data: {
|
||||
name: string
|
||||
color: string
|
||||
value: number[]
|
||||
}[]
|
||||
showXAxis?: boolean
|
||||
inputTokenTotal?: string
|
||||
outputTokenTotal?: string
|
||||
request2xxTotal?: string
|
||||
request4xxTotal?: string
|
||||
request5xxTotal?: string
|
||||
traffic2xxTotal?: string
|
||||
traffic4xxTotal?: string
|
||||
traffic5xxTotal?: string
|
||||
max?: string | number
|
||||
min?: string | number
|
||||
}
|
||||
|
||||
type ServiceBarCharProps = {
|
||||
customClassNames?: string
|
||||
dataInfo?: BarChartInfo
|
||||
height?: number
|
||||
showAvgLine?: boolean
|
||||
showLegendIndicator?: boolean
|
||||
hideIndicatorValue?: boolean
|
||||
}
|
||||
|
||||
const ServiceBarChar = ({
|
||||
customClassNames,
|
||||
dataInfo,
|
||||
height,
|
||||
showAvgLine,
|
||||
showLegendIndicator,
|
||||
hideIndicatorValue
|
||||
}: ServiceBarCharProps) => {
|
||||
const chartRef = useRef<ECharts>(null)
|
||||
const [option, setOption] = useState<EChartsOption | undefined>({})
|
||||
// 使用从主题配置中导入的默认颜色,而不是硬编码的颜色值
|
||||
const [detaultColor] = useState(defaultColor)
|
||||
const [hasData, setHasData] = useState(true)
|
||||
const tokenMap = {
|
||||
inputToken: $t('输入 Token'),
|
||||
outputToken: $t('输出 Token')
|
||||
}
|
||||
const setChartOption = (dataInfo: BarChartInfo) => {
|
||||
const isNumberArray = typeof dataInfo.data[0] !== 'object'
|
||||
const legendData = isNumberArray ? [dataInfo.title] : dataInfo.data.map((item) => item.name)
|
||||
const dataExists = dataInfo.data && dataInfo.data.length > 0
|
||||
// 更新hasData状态
|
||||
setHasData(dataExists)
|
||||
const tooltipFormatter = (params: { name: string; color: string; seriesIndex?: number }) => {
|
||||
let tooltipContent = `<div style="min-width:140px;padding:8px;">
|
||||
<div>${isNumberArray ? '' : params.name}</div>`
|
||||
const data = isNumberArray
|
||||
? [
|
||||
{
|
||||
name: params.name,
|
||||
color: detaultColor,
|
||||
value: dataInfo.data
|
||||
}
|
||||
]
|
||||
: dataInfo.data
|
||||
// 为每个数据系列添加一行
|
||||
data.forEach((item, index) => {
|
||||
// 使用与柱状图相同的颜色策略,确保颜色一致性
|
||||
const color = item.color ? item.color : index < chartColors.length ? chartColors[index] : detaultColor
|
||||
const name = tokenMap[item.name as keyof typeof tokenMap] || item.name
|
||||
const value = item.value[dataInfo.date.indexOf(params.name)] || 0
|
||||
|
||||
const marker = `<span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${color};"></span>`
|
||||
tooltipContent += `<div style="margin-top: ${index === 0 ? 8 : 4}px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>${marker} ${name}</div> <div style="font-weight:bold; margin-left: 20px;">${value}</div>
|
||||
</div>`
|
||||
})
|
||||
|
||||
tooltipContent += '</div>'
|
||||
return tooltipContent
|
||||
}
|
||||
const option: EChartsOption = {
|
||||
title: [
|
||||
{
|
||||
text:
|
||||
'{titleStyle|' +
|
||||
$t(dataInfo.title) +
|
||||
`}${hideIndicatorValue ? '' : '\n\n{valueStyle|' + dataInfo.value + '}'}`,
|
||||
left: '2%',
|
||||
top: '0',
|
||||
textStyle: {
|
||||
rich: {
|
||||
titleStyle: {
|
||||
fontSize: 14,
|
||||
color: '#999999',
|
||||
fontWeight: 'normal',
|
||||
lineHeight: 20
|
||||
},
|
||||
valueStyle: {
|
||||
fontSize: 32,
|
||||
color: '#101010',
|
||||
fontWeight: 500,
|
||||
lineHeight: 40
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '3%',
|
||||
bottom: '0%',
|
||||
top: '110px',
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: dataExists
|
||||
? {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
formatter: function (params: any) {
|
||||
// 如果是数组,取第一个参数的name
|
||||
const param = Array.isArray(params) ? params[0] : params
|
||||
return tooltipFormatter(param)
|
||||
}
|
||||
}
|
||||
: {
|
||||
show: false // 没有数据时不显示tooltip
|
||||
},
|
||||
legend: {
|
||||
show: !isNumberArray,
|
||||
data: legendData,
|
||||
right: '10px',
|
||||
top: hideIndicatorValue ? '10px' : '60px',
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
textStyle: {
|
||||
color: '#333'
|
||||
},
|
||||
icon: 'rect',
|
||||
formatter: function (name: string): string {
|
||||
// 这里可以映射或自定义图例文本
|
||||
const customNames: Record<string, string> = {
|
||||
inputToken: `${$t('输入 Token')} ${showLegendIndicator ? `(${dataInfo.inputTokenTotal})` : ''}`,
|
||||
outputToken: `${$t('输出 Token')} ${showLegendIndicator ? `(${dataInfo.outputTokenTotal})` : ''}`,
|
||||
'2xx': `${'2xx'} ${showLegendIndicator ? `(${dataInfo.request2xxTotal || dataInfo.traffic2xxTotal})` : ''}`,
|
||||
'4xx': `${'4xx'} ${showLegendIndicator ? `(${dataInfo.request4xxTotal || dataInfo.traffic4xxTotal})` : ''}`,
|
||||
'5xx': `${'5xx'} ${showLegendIndicator ? `(${dataInfo.request5xxTotal || dataInfo.traffic5xxTotal})` : ''}`
|
||||
}
|
||||
return customNames[name] || name
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dataInfo.date,
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#ccc'
|
||||
}
|
||||
},
|
||||
show: false
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '',
|
||||
min: 0,
|
||||
...(showAvgLine ? {} : { minInterval: 1 }),
|
||||
show: dataExists, // 没有数据时不显示Y轴
|
||||
splitLine: {
|
||||
show: dataExists, // 没有数据时不显示网格线
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#eee'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: '{value}'
|
||||
}
|
||||
},
|
||||
// 添加数据缩放组件,实现鼠标放大缩小,后续可能需要
|
||||
// dataZoom: [
|
||||
// {
|
||||
// type: 'inside', // 内置的数据区域缩放组件(使用鼠标滚轮缩放)
|
||||
// xAxisIndex: 0, // 设置缩放作用在第一个x轴
|
||||
// filterMode: 'filter',
|
||||
// start: 0,
|
||||
// end: 100
|
||||
// },
|
||||
// {
|
||||
// type: 'slider', // 滑动条型数据区域缩放组件
|
||||
// xAxisIndex: 0,
|
||||
// filterMode: 'filter',
|
||||
// height: 20,
|
||||
// bottom: 0,
|
||||
// start: 0,
|
||||
// end: 100,
|
||||
// handleIcon: 'path://M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
|
||||
// handleSize: '80%',
|
||||
// handleStyle: {
|
||||
// color: '#fff',
|
||||
// shadowBlur: 3,
|
||||
// shadowColor: 'rgba(0, 0, 0, 0.6)',
|
||||
// shadowOffsetX: 2,
|
||||
// shadowOffsetY: 2
|
||||
// },
|
||||
// show: false // 默认隐藏底部的滑动条,可以改为 true 显示
|
||||
// }
|
||||
// ],
|
||||
// 添加空状态提示
|
||||
silent: !dataExists,
|
||||
graphic: !dataExists
|
||||
? [
|
||||
{
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'middle',
|
||||
style: {
|
||||
text: $t('暂无数据'),
|
||||
fontSize: 14,
|
||||
fill: '#999'
|
||||
}
|
||||
}
|
||||
]
|
||||
: [],
|
||||
series: isNumberArray
|
||||
? [
|
||||
{
|
||||
name: dataInfo.title,
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
itemStyle: {
|
||||
color: detaultColor
|
||||
},
|
||||
markLine: showAvgLine
|
||||
? {
|
||||
silent: false,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
type: 'dashed'
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'insideEndTop',
|
||||
formatter: '{c}',
|
||||
color: '#000',
|
||||
fontSize: 10,
|
||||
backgroundColor: 'transparent',
|
||||
padding: [10, 4],
|
||||
borderRadius: 2,
|
||||
distance: -5
|
||||
},
|
||||
emphasis: {
|
||||
lineStyle: {
|
||||
width: 1 // 保持线条宽度不变,禁用默认的悬停加粗
|
||||
},
|
||||
label: {
|
||||
show: false // 悬停时不显示标签
|
||||
}
|
||||
},
|
||||
data: [{ type: 'average', name: 'Avg' }]
|
||||
}
|
||||
: undefined,
|
||||
data: dataInfo.data
|
||||
}
|
||||
]
|
||||
: dataInfo.data.map((item, index) => ({
|
||||
name: item.name,
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
markLine: showAvgLine
|
||||
? {
|
||||
silent: false,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
type: 'dashed'
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'insideEndTop',
|
||||
formatter: '{c}',
|
||||
color: '#000',
|
||||
fontSize: 10,
|
||||
backgroundColor: 'transparent',
|
||||
padding: [10, 4],
|
||||
borderRadius: 2,
|
||||
distance: -5
|
||||
},
|
||||
emphasis: {
|
||||
lineStyle: {
|
||||
width: 1 // 保持线条宽度不变,禁用默认的悬停加粗
|
||||
},
|
||||
label: {
|
||||
show: false // 悬停时不显示标签
|
||||
}
|
||||
},
|
||||
data: [{ type: 'average', name: 'Avg' }]
|
||||
}
|
||||
: undefined,
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
itemStyle: {
|
||||
// 使用主题中的颜色列表,如果索引超出范围则使用项目自带的颜色
|
||||
color: item.color ? item.color : index < chartColors.length ? chartColors[index] : detaultColor
|
||||
},
|
||||
data: item.value
|
||||
}))
|
||||
}
|
||||
const echartsInstance = chartRef.current?.getEchartsInstance()
|
||||
if (echartsInstance) {
|
||||
echartsInstance.setOption(option)
|
||||
}
|
||||
}
|
||||
|
||||
// 使用深度监听来确保图表数据更新
|
||||
useEffect(() => {
|
||||
if (!dataInfo) return
|
||||
|
||||
// 直接获取 ECharts 实例并设置选项
|
||||
const echartsInstance = chartRef.current?.getEchartsInstance()
|
||||
if (echartsInstance) {
|
||||
// 清除已有的图表
|
||||
echartsInstance.clear()
|
||||
// 重新设置选项
|
||||
setChartOption(dataInfo)
|
||||
}
|
||||
}, [dataInfo, JSON.stringify(dataInfo)])
|
||||
|
||||
// 添加窗口大小变化监听,实现自适应
|
||||
useEffect(() => {
|
||||
// 定义resize处理函数
|
||||
const handleResize = () => {
|
||||
const echartsInstance = chartRef.current?.getEchartsInstance()
|
||||
if (echartsInstance) {
|
||||
echartsInstance.resize()
|
||||
}
|
||||
}
|
||||
|
||||
// 添加监听
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
// 组件卸载时移除监听
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<div className={`w-full ${customClassNames}`}>
|
||||
{
|
||||
hideIndicatorValue && (
|
||||
<div className="absolute top-[26px] left-[10px] w-full">
|
||||
<div className="relative top-[5px]">
|
||||
<div className="absolute top-[23px] right-[5%] grid grid-cols-[auto_auto] justify-items-end">
|
||||
<div className="flex justify-center items-center">
|
||||
<span className="text-[#FE564D] text-[9px]">▲</span>
|
||||
</div>
|
||||
<span className="ml-1 text-right">{dataInfo?.max}</span>
|
||||
<div className="flex justify-center items-center">
|
||||
<span className="text-[#27B148] text-[9px]">▼</span>
|
||||
</div>
|
||||
<span className="ml-1 text-right">{dataInfo?.min}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div style={!hasData ? { cursor: 'default', pointerEvents: 'none' } : {}}>
|
||||
<ECharts
|
||||
ref={chartRef}
|
||||
option={option}
|
||||
style={{ height: height || 400 }}
|
||||
opts={{ renderer: 'svg' }}
|
||||
theme="apipark" // 这里应用主题名称,需要先在应用入口注册
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceBarChar
|
||||
@@ -0,0 +1,37 @@
|
||||
import TimeRangeSelector, { RangeValue, TimeRange } from '@common/components/aoplatform/TimeRangeSelector'
|
||||
import { useState } from 'react'
|
||||
|
||||
export type TimeOption = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays'
|
||||
const DateSelectFilter = ({
|
||||
selectCallback,
|
||||
defaultTime,
|
||||
customClassNames
|
||||
}: {
|
||||
selectCallback: (timeRange: TimeRange) => void
|
||||
defaultTime: TimeOption
|
||||
customClassNames?: string
|
||||
}) => {
|
||||
/** 默认时间 */
|
||||
const [timeButton, setTimeButton] = useState<TimeOption>(defaultTime || 'hour')
|
||||
/** 日期选择 */
|
||||
const [datePickerValue, setDatePickerValue] = useState<RangeValue>()
|
||||
/** 时间范围变化 */
|
||||
const handleTimeRangeChange = (timeRange: TimeRange) => {
|
||||
selectCallback(timeRange)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TimeRangeSelector
|
||||
labelSize="small"
|
||||
customClassNames={customClassNames}
|
||||
initialTimeButton={timeButton}
|
||||
onTimeButtonChange={setTimeButton}
|
||||
initialDatePickerValue={datePickerValue}
|
||||
onTimeRangeChange={handleTimeRangeChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DateSelectFilter
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Button, Card } from 'antd'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { $t } from '@common/locales'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
|
||||
/** 服务指标 */
|
||||
type IndicatorType = {
|
||||
title: string
|
||||
link?: string
|
||||
content: string | React.ReactNode
|
||||
}
|
||||
const Indicator = ({ indicatorInfo }: { indicatorInfo: any }) => {
|
||||
/** 服务指标 */
|
||||
const [indicatorList, setIndicator] = useState<IndicatorType[]>([])
|
||||
/** 路由跳转 */
|
||||
const navigateTo = useNavigate()
|
||||
|
||||
/** 设置服务指标 */
|
||||
const setIndicatorList = () => {
|
||||
const side = indicatorInfo?.serviceKind === 'ai' ? 'aiInside' : 'inside'
|
||||
setIndicator([
|
||||
{
|
||||
title: indicatorInfo?.enableMcp ? 'APIs / Tools' : 'APIs',
|
||||
link: `/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/route`,
|
||||
content: indicatorInfo?.apiNum ?? 0
|
||||
},
|
||||
{
|
||||
title: $t('订阅数量'),
|
||||
link: `/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/subscriber`,
|
||||
content: indicatorInfo?.subscriberNum ?? 0
|
||||
},
|
||||
{
|
||||
title: 'MCP',
|
||||
link: `/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/setting`,
|
||||
content: (
|
||||
<>
|
||||
<Button
|
||||
color={indicatorInfo?.enableMcp ? 'green' : 'primary'}
|
||||
className="w-full rounded-[10px]"
|
||||
variant="outlined"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
navigateTo(`/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/setting`)
|
||||
}}
|
||||
>
|
||||
{indicatorInfo?.enableMcp ? $t('已开启') : $t('开启 MCP')}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!indicatorInfo) return
|
||||
setIndicatorList()
|
||||
}, [indicatorInfo])
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
{indicatorList.map((item, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 cursor-pointer rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'py-[20px] px-[18px]'
|
||||
}}
|
||||
onClick={() => {
|
||||
if (item.link) {
|
||||
navigateTo(item.link)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="text-[14px] text-[#999999] mb-[10px]" style={{ fontFamily: 'Microsoft YaHei' }}>
|
||||
{item.title}
|
||||
{item.link && <Icon icon="uiw:right" width="16" height="16" className="absolute top-[14px] right-[14px]" />}
|
||||
</div>
|
||||
<div className={`${index < 2 ? 'text-[32px] font-medium text-[#101010]' : 'block mt-[30px]'}`} style={{ fontFamily: 'Microsoft YaHei' }}>
|
||||
{item.content}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Indicator
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useMemo, useRef, useEffect } from 'react'
|
||||
import PageList from '@common/components/aoplatform/PageList'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { Card } from 'antd'
|
||||
import { AI_SERVICE_TOP_RANKING_LIST, REST_SERVICE_TOP_RANKING_LIST } from '@core/const/system/const'
|
||||
|
||||
interface RankingListData {
|
||||
[key: string]: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
request: number;
|
||||
token?: number;
|
||||
traffic?: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface PageListRef {
|
||||
reload: () => void;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 排名列表
|
||||
* @returns
|
||||
*/
|
||||
const RankingList = ({ topRankingList, serviceType }: { topRankingList: RankingListData; serviceType: 'aiService' | 'restService' }) => {
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
/** 表格 ref */
|
||||
const tableRefs = useRef<{ [key: string]: PageListRef | null }>({});
|
||||
/** 列 */
|
||||
const columns = useMemo(() => {
|
||||
return [...(serviceType === 'aiService' ? AI_SERVICE_TOP_RANKING_LIST : REST_SERVICE_TOP_RANKING_LIST)].map((x) => {
|
||||
return {
|
||||
...x,
|
||||
title: typeof x.title === 'string' ? $t(x.title as string) : x.title
|
||||
}
|
||||
})
|
||||
}, [serviceType, state.language])
|
||||
|
||||
/** 监听 serviceType 变化,刷新所有表格 */
|
||||
useEffect(() => {
|
||||
// 重新加载所有表格数据
|
||||
if (Object.keys(tableRefs.current).length > 0) {
|
||||
Object.values(tableRefs.current).forEach(ref => {
|
||||
// 如果组件实例存在并且有reload方法
|
||||
if (ref && typeof ref.reload === 'function') {
|
||||
ref.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [serviceType, topRankingList])
|
||||
|
||||
/**
|
||||
* 获取表格数据
|
||||
* @param item
|
||||
* @returns
|
||||
*/
|
||||
const getTableData = (item: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({ data: topRankingList[item], success: true })
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className="flex w-full pb-[10px]">
|
||||
{Object.keys(topRankingList)?.map((item: any, index: number) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 min-w-[430px] h-fit rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'p-[15px]'
|
||||
}}
|
||||
>
|
||||
<div className="mb-[10px]">
|
||||
<span className="text-[14px] text-[#999999]" style={{ fontFamily: 'Microsoft YaHei' }}>{item === 'TOP API' ? $t('API 使用排名') : $t('消费者使用排名')}</span>
|
||||
</div>
|
||||
<PageList
|
||||
id={item}
|
||||
columns={[...columns]}
|
||||
minVirtualHeight={430}
|
||||
noScroll
|
||||
request={() => getTableData(item)}
|
||||
showPagination={false}
|
||||
tableClass="ranking-list"
|
||||
ref={ref => {
|
||||
if (ref) tableRefs.current[item] = ref;
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RankingList
|
||||
@@ -0,0 +1,428 @@
|
||||
import { Card, Spin } from 'antd'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import Indicator from './indicator/Indicator'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import DateSelectFilter, { TimeOption } from './filter/DateSelectFilter'
|
||||
import { TimeRange } from '@common/components/aoplatform/TimeRangeSelector'
|
||||
import ServiceBarChar, { BarChartInfo } from './charts/ServiceBarChar'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { App } from 'antd'
|
||||
import ServiceAreaChart from './charts/ServiceAreaChart'
|
||||
import RankingList from './rankingList/RankingList'
|
||||
import { abbreviateFloat, formatBytes, formatDuration, formatNumberWithUnit, getTime } from '@dashboard/utils/dashboard'
|
||||
import { setBarChartInfoData } from './utils'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
const ServiceOverview = ({ serviceType }: { serviceType: 'aiService' | 'restService' }) => {
|
||||
/** 路由参数 */
|
||||
const { serviceId, teamId } = useParams<{ serviceId: string; teamId: string }>()
|
||||
/** 面板 loading */
|
||||
const [dashboardLoading, setDashboardLoading] = useState(true)
|
||||
/** 默认时间 */
|
||||
const [defaultTime] = useState<TimeOption>('day')
|
||||
/** 当前选中的时间范围 */
|
||||
const [timeRange, setTimeRange] = useState<TimeRange | undefined>()
|
||||
/** 总数数据 */
|
||||
const [barChartInfo, setBarChartInfo] = useState<any>()
|
||||
/** 平均值数据 */
|
||||
const [perBarChartInfo, setPerBarChartInfo] = useState<any>()
|
||||
/** 指标数据 */
|
||||
const [indicatorInfo, setIndicatorInfo] = useState<any>([])
|
||||
/** 排名表格数据 */
|
||||
const [topRankingList, setTopRankingList] = useState<any>([])
|
||||
/** 获取服务信息 */
|
||||
const { fetchData } = useFetch()
|
||||
/** 弹窗组件 */
|
||||
const { message } = App.useApp()
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
/** AI 服务数据 */
|
||||
const [aiServiceOverview, setAiServiceOverview] = useState<any>()
|
||||
/** REST 服务数据 */
|
||||
const [restServiceOverview, setRestServiceOverview] = useState<any>()
|
||||
/** 时间选择回调 */
|
||||
const selectCallback = (date: TimeRange) => {
|
||||
setTimeRange(date)
|
||||
}
|
||||
|
||||
/** 获取 AI 服务信息 */
|
||||
const getAIServiceOverview = () => {
|
||||
fetchData<BasicResponse<{ overview: any }>>('service/overview/monitor/ai', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId, team: teamId, start: timeRange?.start, end: timeRange?.end },
|
||||
eoTransformKeys: [
|
||||
'enable_mcp',
|
||||
'subscriber_num',
|
||||
'api_num',
|
||||
'service_kind',
|
||||
'avaliable_monitor',
|
||||
'request_overview',
|
||||
'token_overview',
|
||||
'avg_token_overview',
|
||||
'avg_request_per_subscriber_overview',
|
||||
'avg_token_per_subscriber_overview',
|
||||
'request_total',
|
||||
'token_total',
|
||||
'avg_token',
|
||||
'max_token',
|
||||
'min_token',
|
||||
'avg_request_per_subscriber',
|
||||
'avg_token_per_subscriber',
|
||||
'input_token',
|
||||
'output_token',
|
||||
'total_token',
|
||||
'request_2xx_total',
|
||||
'request_4xx_total',
|
||||
'request_5xx_total',
|
||||
'input_token_total',
|
||||
'output_token_total',
|
||||
'max_token_per_subscriber',
|
||||
'min_token_per_subscriber',
|
||||
'max_request_per_subscriber',
|
||||
'min_request_per_subscriber'
|
||||
]
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 存储 AI 服务数据
|
||||
setAiServiceOverview(data.overview)
|
||||
// 设置 AI 报表数据
|
||||
setAiChartInfoData(data.overview)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
setDashboardLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 REST 服务数据
|
||||
* */
|
||||
const setRestChartInfoData = (serviceOverview: any) => {
|
||||
// 设置指标数据
|
||||
setIndicatorInfo({
|
||||
apiNum: serviceOverview.apiNum,
|
||||
subscriberNum: serviceOverview.subscriberNum,
|
||||
teamId: teamId,
|
||||
enableMcp: serviceOverview.enableMcp,
|
||||
serviceKind: serviceOverview.serviceKind,
|
||||
serviceId: serviceId
|
||||
})
|
||||
// 设置总数数据
|
||||
setBarChartInfo([
|
||||
// 服务请求次数
|
||||
{
|
||||
...setBarChartInfoData({
|
||||
title: $t('请求次数'),
|
||||
data: serviceOverview.requestOverview,
|
||||
value: formatNumberWithUnit(serviceOverview.requestTotal),
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
request2xxTotal: formatNumberWithUnit(serviceOverview.request2xxTotal),
|
||||
request4xxTotal: formatNumberWithUnit(serviceOverview.request4xxTotal),
|
||||
request5xxTotal: formatNumberWithUnit(serviceOverview.request5xxTotal)
|
||||
},
|
||||
// 流量消耗总数
|
||||
{
|
||||
...setBarChartInfoData({
|
||||
title: $t('网络流量'),
|
||||
data: serviceOverview.trafficOverview,
|
||||
value: formatBytes(serviceOverview.trafficTotal),
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
traffic2xxTotal: formatBytes(serviceOverview.traffic2xxTotal),
|
||||
traffic4xxTotal: formatBytes(serviceOverview.traffic4xxTotal),
|
||||
traffic5xxTotal: formatBytes(serviceOverview.traffic5xxTotal)
|
||||
}
|
||||
])
|
||||
// 设置平均值数据
|
||||
setPerBarChartInfo([
|
||||
// 各个模型使用量
|
||||
{
|
||||
title: $t('平均响应时间'),
|
||||
data: serviceOverview.avgResponseTimeOverview,
|
||||
value: formatDuration(serviceOverview.avgResponseTime),
|
||||
originValue: serviceOverview.avgResponseTime,
|
||||
date: serviceOverview.date,
|
||||
max: formatDuration(serviceOverview.maxResponseTime),
|
||||
min: formatDuration(serviceOverview.minResponseTime),
|
||||
type: 'area',
|
||||
showXAxis: false
|
||||
},
|
||||
// 平均请求
|
||||
{
|
||||
...setBarChartInfoData({
|
||||
title: $t('平均每消费者的请求次数'),
|
||||
data: serviceOverview.avgRequestPerSubscriberOverview,
|
||||
date: serviceOverview.date,
|
||||
showXAxis: false
|
||||
}),
|
||||
max: abbreviateFloat(serviceOverview.maxRequestPerSubscriber),
|
||||
min: abbreviateFloat(serviceOverview.minRequestPerSubscriber)
|
||||
},
|
||||
// 平均流量消耗
|
||||
{
|
||||
...setBarChartInfoData({
|
||||
title: $t('平均每消费者的网络流量'),
|
||||
data: serviceOverview.avgTrafficPerSubscriberOverview,
|
||||
date: serviceOverview.date,
|
||||
showXAxis: false
|
||||
}),
|
||||
max: formatBytes(serviceOverview.maxTrafficPerSubscriber),
|
||||
min: formatBytes(serviceOverview.minTrafficPerSubscriber)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 AI 服务数据
|
||||
* */
|
||||
const setAiChartInfoData = (serviceOverview: any) => {
|
||||
// 设置指标数据
|
||||
setIndicatorInfo({
|
||||
apiNum: serviceOverview.apiNum,
|
||||
subscriberNum: serviceOverview.subscriberNum,
|
||||
teamId: teamId,
|
||||
enableMcp: serviceOverview.enableMcp,
|
||||
serviceKind: serviceOverview.serviceKind,
|
||||
serviceId: serviceId
|
||||
})
|
||||
// 设置总数数据
|
||||
setBarChartInfo([
|
||||
// 服务请求次数
|
||||
{
|
||||
...setBarChartInfoData({
|
||||
title: $t('请求次数'),
|
||||
data: serviceOverview.requestOverview,
|
||||
value: formatNumberWithUnit(serviceOverview.requestTotal),
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
request2xxTotal: formatNumberWithUnit(serviceOverview.request2xxTotal),
|
||||
request4xxTotal: formatNumberWithUnit(serviceOverview.request4xxTotal),
|
||||
request5xxTotal: formatNumberWithUnit(serviceOverview.request5xxTotal)
|
||||
},
|
||||
// token 消耗总数
|
||||
{
|
||||
...setBarChartInfoData({
|
||||
title: $t('Token 消耗'),
|
||||
data: serviceOverview.tokenOverview.map((item: { inputToken: number; outputToken: number }) => ({
|
||||
inputToken: item.inputToken,
|
||||
outputToken: item.outputToken
|
||||
})),
|
||||
value: formatNumberWithUnit(serviceOverview.tokenTotal),
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
inputTokenTotal: formatNumberWithUnit(serviceOverview.inputTokenTotal),
|
||||
outputTokenTotal: formatNumberWithUnit(serviceOverview.outputTokenTotal)
|
||||
}
|
||||
])
|
||||
// 设置平均值数据
|
||||
setPerBarChartInfo([
|
||||
// 平均 token 消耗
|
||||
{
|
||||
title: $t('平均 Token 消耗'),
|
||||
data: serviceOverview.avgTokenOverview,
|
||||
value: formatNumberWithUnit(serviceOverview.avgToken) + ' Token/s',
|
||||
originValue: serviceOverview.avgToken,
|
||||
date: serviceOverview.date,
|
||||
min: formatNumberWithUnit(serviceOverview.minToken) + ' Token/s',
|
||||
max: formatNumberWithUnit(serviceOverview.maxToken) + ' Token/s',
|
||||
type: 'area'
|
||||
},
|
||||
{
|
||||
// 平均请求
|
||||
...setBarChartInfoData({
|
||||
title: $t('平均每消费者的请求次数'),
|
||||
data: serviceOverview.avgRequestPerSubscriberOverview,
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
max: abbreviateFloat(serviceOverview.maxRequestPerSubscriber),
|
||||
min: abbreviateFloat(serviceOverview.minRequestPerSubscriber)
|
||||
},
|
||||
// 评价 token 消耗
|
||||
{
|
||||
...setBarChartInfoData({
|
||||
title: $t('平均每消费者的 Token 消耗'),
|
||||
data: serviceOverview.avgTokenPerSubscriberOverview.map(
|
||||
(item: { inputToken: number; outputToken: number }) => ({
|
||||
inputToken: item.inputToken,
|
||||
outputToken: item.outputToken
|
||||
})
|
||||
),
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
max: abbreviateFloat(serviceOverview.maxTokenPerSubscriber),
|
||||
min: abbreviateFloat(serviceOverview.minTokenPerSubscriber)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
/** 获取 REST 服务信息 */
|
||||
const getRestServiceOverview = () => {
|
||||
fetchData<BasicResponse<{ overview: any }>>('service/overview/monitor/rest', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId, team: teamId, start: timeRange?.start, end: timeRange?.end },
|
||||
eoTransformKeys: [
|
||||
'enable_mcp',
|
||||
'subscriber_num',
|
||||
'api_num',
|
||||
'service_kind',
|
||||
'avaliable_monitor',
|
||||
'request_overview',
|
||||
'traffic_overview',
|
||||
'avg_request_per_subscriber_overview',
|
||||
'avg_response_time_overview',
|
||||
'avg_traffic_per_subscriber_overview',
|
||||
'request_total',
|
||||
'traffic_total',
|
||||
'max_response_time',
|
||||
'min_response_time',
|
||||
'avg_response_time',
|
||||
'avg_request_per_subscriber',
|
||||
'avg_traffic_per_subscriber',
|
||||
'request_2xx_total',
|
||||
'request_4xx_total',
|
||||
'request_5xx_total',
|
||||
'traffic_2xx_total',
|
||||
'traffic_4xx_total',
|
||||
'traffic_5xx_total',
|
||||
'max_request_per_subscriber',
|
||||
'min_request_per_subscriber',
|
||||
'max_traffic_per_subscriber',
|
||||
'min_traffic_per_subscriber'
|
||||
]
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 存储 REST 服务数据
|
||||
setRestServiceOverview(data.overview)
|
||||
// 设置 REST 报表数据
|
||||
setRestChartInfoData(data.overview)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
setDashboardLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取排名列表 */
|
||||
const getTopRankingList = () => {
|
||||
fetchData<BasicResponse<{ overview: any }>>('service/monitor/top10', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId, team: teamId, start: timeRange?.start, end: timeRange?.end }
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 设置排名表格数据
|
||||
setTopRankingList({
|
||||
'TOP API': data.apis,
|
||||
'TOP Consumer': data.consumers
|
||||
})
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
setDashboardLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const { startTime, endTime } = getTime(defaultTime, [])
|
||||
setTimeRange({
|
||||
start: startTime,
|
||||
end: endTime
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (timeRange) {
|
||||
serviceType === 'aiService' ? getAIServiceOverview() : getRestServiceOverview()
|
||||
getTopRankingList()
|
||||
}
|
||||
}, [timeRange])
|
||||
|
||||
useEffect(() => {
|
||||
if (serviceType === 'aiService') {
|
||||
aiServiceOverview && setAiChartInfoData(aiServiceOverview)
|
||||
} else {
|
||||
restServiceOverview && setRestChartInfoData(restServiceOverview)
|
||||
}
|
||||
}, [state.language])
|
||||
|
||||
return (
|
||||
<Spin
|
||||
className="h-full pb-[20px]"
|
||||
wrapperClassName="h-full min-h-[150px]"
|
||||
indicator={
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ transform: 'scale(1.5)' }}>
|
||||
<LoadingOutlined style={{ fontSize: 30 }} spin />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
spinning={dashboardLoading}
|
||||
>
|
||||
<div className="mr-[30px]">
|
||||
<Indicator indicatorInfo={indicatorInfo} />
|
||||
<div className="mt-[20px]">
|
||||
<DateSelectFilter selectCallback={selectCallback} defaultTime={defaultTime} />
|
||||
</div>
|
||||
<div className="mt-[20px] flex mb-[10px]">
|
||||
{barChartInfo?.map((item: BarChartInfo, index: number) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 min-w-[430px] rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'py-[15px] px-[0px]'
|
||||
}}
|
||||
>
|
||||
<ServiceBarChar
|
||||
showLegendIndicator={true}
|
||||
key={index}
|
||||
height={400}
|
||||
dataInfo={item}
|
||||
customClassNames="flex-1"
|
||||
></ServiceBarChar>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex mb-[10px]">
|
||||
{perBarChartInfo?.map((item: any, index: number) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 rounded-[10px] min-w-[284px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'py-[15px] px-[0px]'
|
||||
}}
|
||||
>
|
||||
{item.type === 'area' ? (
|
||||
<>
|
||||
<ServiceAreaChart
|
||||
key={index}
|
||||
height={270}
|
||||
dataInfo={item}
|
||||
showAvgLine={true}
|
||||
customClassNames="flex-1 relative"
|
||||
></ServiceAreaChart>
|
||||
</>
|
||||
) : (
|
||||
<ServiceBarChar
|
||||
key={index}
|
||||
height={270}
|
||||
dataInfo={item}
|
||||
hideIndicatorValue={true}
|
||||
customClassNames="flex-1"
|
||||
></ServiceBarChar>
|
||||
)}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<RankingList topRankingList={topRankingList} serviceType={serviceType} />
|
||||
</div>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceOverview
|
||||
@@ -0,0 +1,55 @@
|
||||
export type BarData = {
|
||||
title: string
|
||||
value?: string
|
||||
date: string[]
|
||||
data: any[]
|
||||
showXAxis?: boolean
|
||||
}
|
||||
export const setBarChartInfoData = ({ title, value, data, date, showXAxis }: BarData) => {
|
||||
// 首先获取所有的键名(假设所有对象的键名都一样)
|
||||
if (data.length === 0) {
|
||||
return {
|
||||
title,
|
||||
value,
|
||||
date,
|
||||
data: [],
|
||||
showXAxis: !!showXAxis
|
||||
}
|
||||
}
|
||||
if (typeof data[0] !== 'object') {
|
||||
return {
|
||||
title,
|
||||
value,
|
||||
date,
|
||||
data,
|
||||
showXAxis: !!showXAxis
|
||||
}
|
||||
}
|
||||
// 从第一个对象中获取所有键名
|
||||
const keys = Object.keys(data[0])
|
||||
// 定义颜色映射
|
||||
const colorMap: Record<string, string> = {
|
||||
'2xx': '#3ba272',
|
||||
'4xx': '#ffc404',
|
||||
'5xx': '#b92325'
|
||||
}
|
||||
|
||||
// 为每个键创建一个数据集
|
||||
const transformedData = keys.map((key) => {
|
||||
// 为没有映射颜色的键生成随机颜色
|
||||
const color = colorMap[key]
|
||||
|
||||
return {
|
||||
name: key,
|
||||
color: color,
|
||||
value: data.map((item) => item[key])
|
||||
}
|
||||
})
|
||||
return {
|
||||
title,
|
||||
value,
|
||||
date,
|
||||
data: transformedData,
|
||||
showXAxis: !!showXAxis
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { LoadingOutlined } from '@ant-design/icons'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { BasicResponse, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { EntityItem, MemberItem, SimpleTeamItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
@@ -49,7 +48,6 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
const { fetchData } = useFetch()
|
||||
const [teamOptionList, setTeamOptionList] = useState<DefaultOptionType[]>()
|
||||
const navigate = useNavigate()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { setSystemInfo } = useSystemContext()
|
||||
const [showClassify, setShowClassify] = useState<boolean>(true)
|
||||
const [showAI, setShowAI] = useState<boolean>(false)
|
||||
@@ -355,15 +353,6 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
if (serviceId !== undefined) {
|
||||
setOnEdit(true)
|
||||
getSystemInfo()
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigate('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('设置')
|
||||
}
|
||||
])
|
||||
} else {
|
||||
getProviderOptionList()
|
||||
setOnEdit(false)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { EntityItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes'
|
||||
@@ -10,7 +9,7 @@ import { App, Button } from 'antd'
|
||||
import hljs from 'highlight.js'
|
||||
import 'highlight.js/styles/default.css'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
const ServiceInsideDocument = () => {
|
||||
const { message } = App.useApp()
|
||||
const [updater, setUpdater] = useState<string>()
|
||||
@@ -19,8 +18,6 @@ const ServiceInsideDocument = () => {
|
||||
const [doc, setDoc] = useState<string>()
|
||||
const { fetchData } = useFetch()
|
||||
const { serviceId, teamId } = useParams<RouterParams>()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const navigator = useNavigate()
|
||||
const save = () => {
|
||||
fetchData<
|
||||
BasicResponse<{
|
||||
@@ -88,15 +85,6 @@ const ServiceInsideDocument = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('使用说明')
|
||||
}
|
||||
])
|
||||
getServiceDoc()
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import { FC, useEffect, useMemo, useState } from 'react'
|
||||
import { Link, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
import { SystemConfigFieldType } from '../../const/system/type.ts'
|
||||
import { useSystemContext } from '../../contexts/SystemContext.tsx'
|
||||
import ServiceInfoCard from '@common/components/aoplatform/serviceInfoCard.tsx'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
|
||||
const SystemInsidePage: FC = () => {
|
||||
const { message } = App.useApp()
|
||||
@@ -26,12 +28,13 @@ const SystemInsidePage: FC = () => {
|
||||
const [activeMenu, setActiveMenu] = useState<string>()
|
||||
const navigateTo = useNavigate()
|
||||
const [showMenu, setShowMenu] = useState<boolean>(false)
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
|
||||
const getSystemInfo = () => {
|
||||
fetchData<BasicResponse<{ service: SystemConfigFieldType }>>('service/info', {
|
||||
method: 'GET',
|
||||
eoParams: { team: teamId, service: serviceId }
|
||||
}).then(response => {
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setSystemInfo(data.service)
|
||||
@@ -47,7 +50,7 @@ const SystemInsidePage: FC = () => {
|
||||
fetchData<BasicResponse<{ prefix: string; force: boolean }>>('service/router/define', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId, team: teamId }
|
||||
}).then(response => {
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setApiPrefix(data.prefix)
|
||||
@@ -65,6 +68,7 @@ const SystemInsidePage: FC = () => {
|
||||
'assets',
|
||||
null,
|
||||
[
|
||||
getItem(<Link to="./overview">{$t('总览')}</Link>, 'overview', undefined, undefined, undefined, ''),
|
||||
getItem(
|
||||
<Link to="./route">{$t('API 路由')}</Link>,
|
||||
'route',
|
||||
@@ -146,9 +150,10 @@ const SystemInsidePage: FC = () => {
|
||||
null,
|
||||
[
|
||||
// APP_MODE === 'pro' ? getItem(<Link to="./topology">{$t('调用拓扑图')}</Link>, 'topology',undefined,undefined,undefined,'project.mySystem.topology.view'):null,
|
||||
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting', undefined, undefined, undefined, ''),
|
||||
getItem(
|
||||
<Link to="./setting">{$t('设置')}</Link>,
|
||||
'setting',
|
||||
<Link to="./logs">{$t('日志')}</Link>,
|
||||
'logs',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
@@ -166,12 +171,11 @@ const SystemInsidePage: FC = () => {
|
||||
const newMenu = cloneDeep(menu)
|
||||
return newMenu!.filter((m: MenuItemGroupType) => {
|
||||
if (m && m.children && m.children.length > 0) {
|
||||
m.children = m.children.filter(c => {
|
||||
m.children = m.children.filter((c) => {
|
||||
if (!c) return false
|
||||
return (c as MenuItemType & { access: string }).access
|
||||
? checkPermission(
|
||||
(c as MenuItemType & { access: string })
|
||||
.access as keyof (typeof PERMISSION_DEFINITION)[0]
|
||||
(c as MenuItemType & { access: string }).access as keyof (typeof PERMISSION_DEFINITION)[0]
|
||||
)
|
||||
: true
|
||||
})
|
||||
@@ -180,12 +184,8 @@ const SystemInsidePage: FC = () => {
|
||||
})
|
||||
}
|
||||
const filteredMenu = filterMenu(SYSTEM_PAGE_MENU_ITEMS as MenuItemGroupType<MenuItemType>[])
|
||||
const menu =
|
||||
(activeMenu ?? filteredMenu[0]?.children)
|
||||
? filteredMenu[0]?.children?.[0]?.key
|
||||
: filteredMenu[0]?.key
|
||||
if (menu && currentUrl.split('/')[-1] !== menu)
|
||||
navigateTo(`/service/${teamId}/inside/${serviceId}/${menu}`)
|
||||
const menu = (activeMenu ?? filteredMenu[0]?.children) ? filteredMenu[0]?.children?.[0]?.key : filteredMenu[0]?.key
|
||||
if (menu && currentUrl.split('/')[-1] !== menu) navigateTo(`/service/${teamId}/inside/${serviceId}/${menu}`)
|
||||
return filteredMenu || []
|
||||
}, [accessData, accessInit, SYSTEM_PAGE_MENU_ITEMS])
|
||||
|
||||
@@ -208,7 +208,7 @@ const SystemInsidePage: FC = () => {
|
||||
} else if (serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]) {
|
||||
setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1])
|
||||
} else {
|
||||
setActiveMenu('route')
|
||||
setActiveMenu('overview')
|
||||
}
|
||||
}, [currentUrl])
|
||||
|
||||
@@ -219,10 +219,19 @@ const SystemInsidePage: FC = () => {
|
||||
}, [accessData])
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigateTo('/service/list')
|
||||
},
|
||||
{
|
||||
title: systemInfo?.name || ''
|
||||
}
|
||||
])
|
||||
if (activeMenu && serviceId === currentUrl.split('/')[currentUrl.split('/').length - 1]) {
|
||||
navigateTo(`/service/${teamId}/inside/${serviceId}/${activeMenu}`)
|
||||
}
|
||||
}, [activeMenu])
|
||||
}, [activeMenu, systemInfo, state.language])
|
||||
|
||||
useEffect(() => {
|
||||
serviceId && getSystemInfo()
|
||||
@@ -233,16 +242,7 @@ const SystemInsidePage: FC = () => {
|
||||
{showMenu ? (
|
||||
<InsidePage
|
||||
pageTitle={systemInfo?.name || '-'}
|
||||
tagList={[
|
||||
...(systemInfo?.enable_mcp ? [{ label: 'MCP', color: '#FFF0C1', className: 'text-[#000]' }] : []),
|
||||
{
|
||||
label: (
|
||||
<Paragraph className="mb-0" copyable={serviceId ? { text: serviceId } : false}>
|
||||
{$t('服务 ID')}:{serviceId || '-'}
|
||||
</Paragraph>
|
||||
)
|
||||
}
|
||||
]}
|
||||
customBanner={<ServiceInfoCard serviceId={serviceId} teamId={teamId} />}
|
||||
backUrl="/service/list"
|
||||
>
|
||||
<div className="flex flex-1 h-full">
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
STATUS_CODE
|
||||
} from '@common/const/const.tsx'
|
||||
import { SimpleMemberItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
@@ -20,7 +19,7 @@ import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import { App, Form, TreeSelect } from 'antd'
|
||||
import { DefaultOptionType } from 'antd/es/cascader'
|
||||
import { FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { SYSTEM_SUBSCRIBER_TABLE_COLUMNS } from '../../const/system/const.tsx'
|
||||
import {
|
||||
SimpleSystemItem,
|
||||
@@ -31,7 +30,6 @@ import {
|
||||
} from '../../const/system/type.ts'
|
||||
|
||||
const SystemInsideSubscriber: FC = () => {
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal, message } = App.useApp()
|
||||
const { fetchData } = useFetch()
|
||||
const { serviceId, teamId } = useParams<RouterParams>()
|
||||
@@ -39,7 +37,6 @@ const SystemInsideSubscriber: FC = () => {
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
|
||||
const { accessData, state } = useGlobalContext()
|
||||
const navigator = useNavigate()
|
||||
const getSystemSubscriber = () => {
|
||||
return fetchData<BasicResponse<{ subscribers: SystemSubscriberTableListItem[] }>>(
|
||||
'service/subscribers',
|
||||
@@ -162,15 +159,6 @@ const SystemInsideSubscriber: FC = () => {
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('订阅方管理')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId])
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ZoomInOutlined, ZoomOutOutlined } from '@ant-design/icons'
|
||||
import G6, { EdgeConfig, Graph, NodeConfig } from '@antv/g6'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { EntityItem } from '@common/const/type'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { $t } from '@common/locales'
|
||||
import { getNodeSpacing } from '@common/utils/systemRunning'
|
||||
@@ -10,7 +9,7 @@ import { RouterParams } from '@core/components/aoplatform/RenderRoutes'
|
||||
import { App, Button } from 'antd'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { RELATIVE_PICTURE_NODE_FONTSIZE } from '../../const/system-running/const'
|
||||
import { GraphData } from '../../const/system-running/type'
|
||||
import { SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP } from '../../const/system/const'
|
||||
@@ -26,9 +25,7 @@ export default function SystemTopology() {
|
||||
const [graph, setGraph] = useState<Graph | null>(null)
|
||||
const { fetchData } = useFetch()
|
||||
const { systemInfo } = useSystemContext()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [zoomNum, setZoomNum] = useState<number>(1)
|
||||
const navigate = useNavigate()
|
||||
|
||||
|
||||
const getNodeData = () => {
|
||||
@@ -105,15 +102,6 @@ export default function SystemTopology() {
|
||||
|
||||
useEffect(() => {
|
||||
getNodeData()
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigate('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('调用拓扑图')
|
||||
}
|
||||
])
|
||||
}, [serviceId])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -9,13 +9,12 @@ import { $t } from '@common/locales/index.ts'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import { Button, Empty, Spin, Upload, message } from 'antd'
|
||||
import { forwardRef, useEffect, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import {
|
||||
SystemApiDetail,
|
||||
SystemInsideApiDocumentHandle,
|
||||
SystemInsideApiDocumentProps
|
||||
} from '../../../const/system/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
|
||||
const SystemInsideApiDocument = forwardRef<
|
||||
SystemInsideApiDocumentHandle,
|
||||
@@ -26,18 +25,7 @@ const SystemInsideApiDocument = forwardRef<
|
||||
const [apiDetail, setApiDetail] = useState<SystemApiDetail>()
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [showEditor, setShowEditor] = useState<boolean>(false)
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const navigator = useNavigate()
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('API 文档')
|
||||
}
|
||||
])
|
||||
getApiDetail()
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
SystemInsideRouterCreateHandle,
|
||||
SystemInsideRouterCreateProps
|
||||
} from '../../../const/system/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
|
||||
const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, SystemInsideRouterCreateProps>(
|
||||
(props, ref) => {
|
||||
@@ -39,14 +38,12 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
const { state } = useGlobalContext()
|
||||
const { apiPrefix, prefixForce } = useSystemContext()
|
||||
const navigator = useNavigate()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
|
||||
|
||||
const onFinish = () => {
|
||||
return Promise.all([proxyRef.current?.validate?.(), form.validateFields()]).then(([, formValue]) => {
|
||||
const body = {
|
||||
...formValue,
|
||||
path: `${prefixForce ? apiPrefix + '/' : ''}${formValue.path.trim()}${formValue.pathMatch === 'prefix' ? '/*' : ''}`,
|
||||
path: `${prefixForce ? apiPrefix + (!formValue.path?.trim() ? '': '/') : ''}${(formValue.path?.trim() || '')}${formValue.pathMatch === 'prefix' ? '/*' : ''}`,
|
||||
proxy: {
|
||||
...formValue.proxy,
|
||||
path: formValue.proxy.path
|
||||
@@ -118,7 +115,7 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
.then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const { disable, protocols, path, methods, description, match, proxy } = data.router
|
||||
const { disable, protocols, path, name, methods, description, match, proxy } = data.router
|
||||
let newPath = path
|
||||
let pathMatch = 'full'
|
||||
if (prefixForce && path?.startsWith(apiPrefix + '/')) {
|
||||
@@ -131,6 +128,7 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
form.setFieldsValue({
|
||||
disable,
|
||||
protocols,
|
||||
name,
|
||||
path: newPath,
|
||||
pathMatch,
|
||||
methods,
|
||||
@@ -147,19 +145,6 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('API'),
|
||||
onClick: () => navigator(`/service/${teamId}/inside/${serviceId}/route`)
|
||||
},
|
||||
{
|
||||
title: routeId ? $t('编辑 API') : $t('添加 API')
|
||||
}
|
||||
])
|
||||
if (routeId) {
|
||||
getRouterConfig()
|
||||
} else {
|
||||
@@ -253,6 +238,14 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item<SystemApiProxyFieldType>
|
||||
className="flex-1"
|
||||
label={$t('路由名称')}
|
||||
name="name"
|
||||
rules={[{ required: true, whitespace: true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<SystemApiProxyFieldType> label={$t('请求协议')} name="protocols" rules={[{ required: true }]}>
|
||||
<Select
|
||||
|
||||
@@ -3,7 +3,6 @@ import PageList, { PageProColumns } from '@common/components/aoplatform/PageList
|
||||
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx'
|
||||
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { SimpleMemberItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
@@ -11,13 +10,12 @@ import { checkAccess } from '@common/utils/permission.ts'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import { App, Divider, Typography } from 'antd'
|
||||
import { FC, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { SYSTEM_API_TABLE_COLUMNS } from '../../../const/system/const.tsx'
|
||||
import { SystemApiTableListItem } from '../../../const/system/type.ts'
|
||||
|
||||
const SystemInsideRouterList: FC = () => {
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal, message } = App.useApp()
|
||||
const [tableListDataSource, setTableListDataSource] = useState<SystemApiTableListItem[]>([])
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true)
|
||||
@@ -162,17 +160,6 @@ const SystemInsideRouterList: FC = () => {
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId])
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('路由')
|
||||
}
|
||||
])
|
||||
}, [state.language])
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return [...SYSTEM_API_TABLE_COLUMNS].map((x) => {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
|
||||
import {ActionType} from "@ant-design/pro-components";
|
||||
import {FC, useEffect, useMemo, useRef, useState} from "react";
|
||||
import {Link, useLocation, useNavigate, useParams} from "react-router-dom";
|
||||
import { useLocation, useParams} from "react-router-dom";
|
||||
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx";
|
||||
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {App, Button} from "antd";
|
||||
import {
|
||||
SUBSCRIBE_APPROVAL_INNER_DONE_TABLE_COLUMN,
|
||||
@@ -26,7 +25,6 @@ import { SubscribeApprovalInfoType } from "@common/const/approval/type.tsx";
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
const SystemInsideApprovalList:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal,message } = App.useApp()
|
||||
const {serviceId, teamId} = useParams<RouterParams>();
|
||||
const [init, setInit] = useState<boolean>(true)
|
||||
@@ -40,7 +38,6 @@ const SystemInsideApprovalList:FC = ()=>{
|
||||
const [approvalBtnLoading,setApprovalBtnLoading] = useState<boolean>(false)
|
||||
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
|
||||
const {accessData,state} = useGlobalContext()
|
||||
const navigator = useNavigate()
|
||||
|
||||
const openModal = async (type:'approval'|'view',entity:SubscribeApprovalTableListItem)=>{
|
||||
message.loading($t(RESPONSE_TIPS.loading))
|
||||
@@ -142,15 +139,6 @@ const SystemInsideApprovalList:FC = ()=>{
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('订阅审核')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
|
||||
import { Tabs } from "antd"
|
||||
import { useState, useEffect, FC, useMemo } from "react"
|
||||
import { Link, Outlet, useLocation, useNavigate } from "react-router-dom"
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom"
|
||||
import { SYSTEM_PUBLISH_TAB_ITEMS } from "../../../const/system/const"
|
||||
import { $t } from "@common/locales"
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
|
||||
|
||||
const SystemInsidePublic:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const query =new URLSearchParams(useLocation().search)
|
||||
const location = useLocation()
|
||||
const currentUrl = location.pathname
|
||||
@@ -25,18 +23,6 @@ const SystemInsidePublic:FC = ()=>{
|
||||
setPageStatus(Number(query.get('status') ||0) as 0|1)
|
||||
}, [currentUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigateTo('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('发布')
|
||||
}
|
||||
])
|
||||
}, []);
|
||||
|
||||
const tabItems = useMemo(()=>SYSTEM_PUBLISH_TAB_ITEMS?.map((x)=>({...x, label:$t(x.label as string) })),[state.language])
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { ActionType, ParamsType } from "@ant-design/pro-components";
|
||||
import { App, Button, Divider } from "antd";
|
||||
import { useState, useRef, useEffect, useMemo, FC } from "react";
|
||||
import { useParams, Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { useParams, useLocation } from "react-router-dom";
|
||||
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList";
|
||||
import { PublishApprovalModalContent } from "@common/components/aoplatform/PublishApprovalModalContent";
|
||||
import { PUBLISH_APPROVAL_RECORD_INNER_TABLE_COLUMN, PUBLISH_APPROVAL_VERSION_INNER_TABLE_COLUMN, PublishApplyStatusEnum, PublishStatusEnum, PublishTableStatusColorClass } from "@common/const/approval/const";
|
||||
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
|
||||
import { RouterParams, SimpleMemberItem } from "@common/const/type.ts";
|
||||
import { MemberTableListItem } from "../../../const/member/type";
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext";
|
||||
import { useFetch } from "@common/hooks/http";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission";
|
||||
import { SystemPublishReleaseItem } from "../../../const/system/type";
|
||||
@@ -22,7 +21,6 @@ import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
const SystemInsidePublicList:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal,message } = App.useApp()
|
||||
const pageListRef = useRef<ActionType>(null);
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true);
|
||||
@@ -44,7 +42,6 @@ const SystemInsidePublicList:FC = ()=>{
|
||||
const [drawerData, setDrawerData] = useState<PublishTableListItem|PublishVersionTableListItem >({} as PublishTableListItem)
|
||||
const [drawerOkTitle, setDrawerOkTitle] = useState<string>('确认')
|
||||
const [isOkToPublish, setIsOkToPublish] = useState<boolean>(false)
|
||||
const navigateTo = useNavigate()
|
||||
const getSystemPublishList = (params?: ParamsType & {
|
||||
pageSize?: number | undefined;
|
||||
current?: number | undefined;
|
||||
@@ -350,15 +347,6 @@ const SystemInsidePublicList:FC = ()=>{
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigateTo('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('发布')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
@@ -8,11 +8,10 @@ import EditableTable from "@common/components/aoplatform/EditableTable.tsx";
|
||||
import EditableTableWithModal from "@common/components/aoplatform/EditableTableWithModal.tsx";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { UPSTREAM_TYPE_OPTIONS, SYSTEM_UPSTREAM_GLOBAL_CONFIG_TABLE_COLUMNS, schemeOptions, UPSTREAM_BALANCE_OPTIONS, UPSTREAM_PASS_HOST_OPTIONS, PROXY_HEADER_CONFIG, UPSTREAM_PROXY_HEADER_TYPE_OPTIONS } from "../../../const/system/const.tsx";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx";
|
||||
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE } from "@common/const/const.tsx";
|
||||
import { useFetch } from "@common/hooks/http.ts";
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
|
||||
|
||||
@@ -33,10 +32,8 @@ const SystemInsideUpstreamContent= forwardRef<SystemInsideUpstreamContentHandle>
|
||||
const {fetchData} = useFetch()
|
||||
const [, forceUpdate] = useState<unknown>(null);
|
||||
const [formShowHost, setFormShowHost] = useState<boolean>(false);
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [form] = Form.useForm();
|
||||
const {state} = useGlobalContext()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
save:()=>formRef.current?.save()
|
||||
@@ -109,16 +106,7 @@ const globalConfigNodesRule: FormItemProps['rules'] = [
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigate('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('上游')
|
||||
}])
|
||||
|
||||
getUpstreamInfo();
|
||||
getUpstreamInfo();
|
||||
}, [serviceId]);
|
||||
|
||||
const typeOptions = useMemo(()=>UPSTREAM_TYPE_OPTIONS.map(x=>({...x, label:$t(x.label)})),[state.language])
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useFetch } from '@common/hooks/http.ts'
|
||||
import { DefaultOptionType } from 'antd/es/cascader'
|
||||
import { TeamConfigFieldType } from '../../const/team/type.ts'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useTeamContext } from '../../contexts/TeamContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
@@ -32,7 +31,6 @@ const TeamConfig = forwardRef<TeamConfigHandle, TeamConfigProps>((props, ref) =>
|
||||
const currentUrl = location.pathname
|
||||
const { fetchData } = useFetch()
|
||||
const [managerOption, setManagerOption] = useState<DefaultOptionType[]>([])
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { setTeamInfo } = useTeamContext()
|
||||
const { checkPermission, accessInit, state } = useGlobalContext()
|
||||
const pageType = useMemo(() => {
|
||||
@@ -133,15 +131,6 @@ const TeamConfig = forwardRef<TeamConfigHandle, TeamConfigProps>((props, ref) =>
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('团队'),
|
||||
onClick: () => navigateTo('/team/list')
|
||||
},
|
||||
{ title: $t('设置') }
|
||||
])
|
||||
}, [state.language])
|
||||
useEffect(() => {
|
||||
|
||||
getManagerList()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx'
|
||||
import { ActionType } from '@ant-design/pro-components'
|
||||
import { FC, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { App, Button, Modal, Select } from 'antd'
|
||||
import { BasicResponse, COLUMNS_TITLE, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
@@ -59,7 +58,6 @@ export const addMemberToDepartment = (
|
||||
|
||||
const TeamInsideMember: FC = () => {
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal, message } = App.useApp()
|
||||
const { fetchData } = useFetch()
|
||||
const { teamId } = useParams<RouterParams>()
|
||||
@@ -73,7 +71,6 @@ const TeamInsideMember: FC = () => {
|
||||
const [addMemberBtnDisabled, setAddMemberBtnDisabled] = useState<boolean>(true)
|
||||
const [allMemberSelectedDepartIds, setAllMemberSelectedDepartIds] = useState<string[]>([])
|
||||
const [roleList, setRoleList] = useState<EntityItem[]>([])
|
||||
const navigator = useNavigate()
|
||||
|
||||
const operation: PageProColumns<TeamMemberTableListItem>[] = [
|
||||
{
|
||||
@@ -355,13 +352,6 @@ const TeamInsideMember: FC = () => {
|
||||
}, [teamId])
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('团队'),
|
||||
onClick: () => navigator('/team/list')
|
||||
},
|
||||
{ title: $t('成员') }
|
||||
])
|
||||
getRoleList()
|
||||
}, [state.language])
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { PERMISSION_DEFINITION } from "@common/const/permissions.ts";
|
||||
import { TeamConfigType } from "@core/const/team/type.ts";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
import { getItem } from "@common/utils/navigation.tsx";
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx";
|
||||
|
||||
const TeamInsidePage:FC = ()=> {
|
||||
const { message } = App.useApp()
|
||||
@@ -26,6 +27,7 @@ const TeamInsidePage:FC = ()=> {
|
||||
const {getTeamAccessData,cleanTeamAccessData,accessData,checkPermission,teamDataFlushed,accessInit,state} = useGlobalContext()
|
||||
const navigateTo = useNavigate()
|
||||
const [activeMenu, setActiveMenu] = useState<string>()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
|
||||
const onMenuClick: MenuProps['onClick'] = ({key}) => {
|
||||
setActiveMenu(key)
|
||||
@@ -88,6 +90,16 @@ const TeamInsidePage:FC = ()=> {
|
||||
}
|
||||
},[activeMenu])
|
||||
|
||||
useEffect(()=>{
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('团队'),
|
||||
onClick: () => navigateTo('/team/list')
|
||||
},
|
||||
{ title: teamInfo?.name || '-' }
|
||||
])
|
||||
},[state.language, teamInfo])
|
||||
|
||||
useEffect(()=>{
|
||||
getTeamInfo()
|
||||
teamId && getTeamAccessData(teamId)
|
||||
|
||||
@@ -62,6 +62,17 @@ export default function MonitorApiPage(props: MonitorApiPageProps) {
|
||||
getProjectList()
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* 重置时间范围
|
||||
*/
|
||||
let resetTimeRange = () => {}
|
||||
/**
|
||||
* 绑定时间范围组件
|
||||
* @param instance
|
||||
*/
|
||||
const bindRef = (instance: any) => {
|
||||
resetTimeRange = instance.reset
|
||||
}
|
||||
const getApiList = (projectIds?: string[]) => {
|
||||
return fetchData<{ apis: EntityItem[] }>('simple/service/apis', {
|
||||
method: 'POST',
|
||||
@@ -146,6 +157,7 @@ export default function MonitorApiPage(props: MonitorApiPageProps) {
|
||||
}
|
||||
|
||||
const clearSearch = () => {
|
||||
resetTimeRange()
|
||||
setTimeButton('hour')
|
||||
setDatePickerValue(null)
|
||||
setQueryData(undefined)
|
||||
@@ -186,11 +198,17 @@ export default function MonitorApiPage(props: MonitorApiPageProps) {
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setQueryBtnLoading(true)
|
||||
getApiTableList()
|
||||
}, [queryData])
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden h-full pr-PAGE_INSIDE_X">
|
||||
<ScrollableSection>
|
||||
<div className="pl-btnbase pr-btnrbase pb-btnbase content-before">
|
||||
<TimeRangeSelector
|
||||
bindRef={bindRef}
|
||||
labelSize="small"
|
||||
initialTimeButton={timeButton}
|
||||
onTimeButtonChange={setTimeButton}
|
||||
@@ -235,12 +253,8 @@ export default function MonitorApiPage(props: MonitorApiPageProps) {
|
||||
<div className="w-[346px] inline-block">
|
||||
{/* <SearchInputGroup eoSingle={false} eoInputVal={queryData.path} eoClick={() => setQueryData({ ...queryData, path: '' })} /> */}
|
||||
<Input
|
||||
value={queryData?.path}
|
||||
onChange={(e) =>
|
||||
debounce((e) => {
|
||||
setQueryData((prevData) => ({ ...(prevData || {}), path: e.target.value }))
|
||||
}, 100)(e)
|
||||
}
|
||||
value={queryData?.path || ''}
|
||||
onChange={(e) => setQueryData((prevData) => ({ ...(prevData || {}), path: e.target.value }))}
|
||||
allowClear
|
||||
placeholder={$t('请输入请求路径进行搜索')}
|
||||
prefix={<SearchOutlined className="cursor-pointer" />}
|
||||
@@ -249,17 +263,6 @@ export default function MonitorApiPage(props: MonitorApiPageProps) {
|
||||
<Button className="ml-btnybase" onClick={clearSearch}>
|
||||
{$t('重置')}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={queryBtnLoading}
|
||||
className="ml-btnybase"
|
||||
onClick={() => {
|
||||
setQueryBtnLoading(true)
|
||||
getApiTableList()
|
||||
}}
|
||||
>
|
||||
{$t('查询')}
|
||||
</Button>
|
||||
<Button className="ml-btnybase" loading={exportLoading} onClick={exportData}>
|
||||
{$t('导出')}
|
||||
</Button>
|
||||
|
||||
@@ -58,7 +58,17 @@ export default function MonitorAppPage(props: MonitorAppPageProps) {
|
||||
getMonitorData()
|
||||
getAppList()
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* 重置时间范围
|
||||
*/
|
||||
let resetTimeRange = () => {}
|
||||
/**
|
||||
* 绑定时间范围组件
|
||||
* @param instance
|
||||
*/
|
||||
const bindRef = (instance: any) => {
|
||||
resetTimeRange = instance.reset
|
||||
}
|
||||
const getMonitorData = () => {
|
||||
let query = queryData
|
||||
if (!queryData || queryData.start === undefined) {
|
||||
@@ -86,6 +96,7 @@ export default function MonitorAppPage(props: MonitorAppPageProps) {
|
||||
}
|
||||
|
||||
const clearSearch = () => {
|
||||
resetTimeRange()
|
||||
setTimeButton('hour')
|
||||
setDatePickerValue(null)
|
||||
setQueryData({ type: 'subscriber' })
|
||||
@@ -163,10 +174,16 @@ export default function MonitorAppPage(props: MonitorAppPageProps) {
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setQueryBtnLoading(true)
|
||||
getAppTableList()
|
||||
}, [queryData])
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-hidden pr-PAGE_INSIDE_X">
|
||||
<div className="pl-btnbase pr-btnrbase pb-btnybase">
|
||||
<TimeRangeSelector
|
||||
bindRef={bindRef}
|
||||
initialTimeButton={timeButton}
|
||||
onTimeButtonChange={setTimeButton}
|
||||
initialDatePickerValue={datePickerValue}
|
||||
@@ -194,17 +211,6 @@ export default function MonitorAppPage(props: MonitorAppPageProps) {
|
||||
<Button className="ml-btnybase" onClick={clearSearch}>
|
||||
{$t('重置')}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={queryBtnLoading}
|
||||
className="ml-btnybase"
|
||||
onClick={() => {
|
||||
setQueryBtnLoading(true)
|
||||
getAppTableList()
|
||||
}}
|
||||
>
|
||||
{$t('查询')}
|
||||
</Button>
|
||||
<Button className="ml-btnybase" loading={exportLoading} onClick={exportData}>
|
||||
{$t('导出')}
|
||||
</Button>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user