mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
Compare commits
260 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1942344373 | |||
| 96ad58fe28 | |||
| abb4963bac | |||
| a4642ec6e6 | |||
| 6ee1996e6f | |||
| 9ba746ba7f | |||
| fdac169bda | |||
| 3026e24bab | |||
| 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 | |||
| 4eb3368875 | |||
| 4478e6823a | |||
| ab5bffea87 | |||
| 8a48828a76 | |||
| c2b70e23e4 | |||
| eb46a4365c | |||
| 058a8f7974 | |||
| b5585f548a | |||
| e4a3e1a1a2 | |||
| 674a15ef32 | |||
| d82d665280 | |||
| 752db42b3b | |||
| 10aaf85a26 | |||
| 5c97ef9416 | |||
| 5fc84299f1 | |||
| 2eeeebf7c2 | |||
| 155ad537a9 | |||
| 4a1430c62a | |||
| 8cc0d038bd | |||
| 165759398e | |||
| 1091d4e086 | |||
| 0523f13dfb | |||
| 256c04f5bb | |||
| a9dcc78db6 | |||
| 1f6c173e18 | |||
| b593e8b57b | |||
| 1ec00de03c | |||
| bad7fbadda | |||
| 7a506fc15e | |||
| dec2c3a23e | |||
| 2093541c37 | |||
| 40c7ba4305 | |||
| a95bca31e2 | |||
| a541e45a53 | |||
| b20c66b311 | |||
| 729e1f105c | |||
| 6aa96a2ae9 | |||
| 5093c98656 | |||
| f4b70d4e71 | |||
| eeb36f43a4 | |||
| 5a59a6d378 | |||
| 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:
|
||||
|
||||
@@ -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,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="356px" height="48px" viewBox="0 0 356 48" enable-background="new 0 0 356 48" xml:space="preserve"> <image id="image0" width="356" height="48" x="0" y="0"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAWQAAAAwCAMAAAARrfBHAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo
|
||||
AAB1MAAA6mAAADqYAAAXcJy6UTwAAAEmUExURQAAACBAQCAwQCoqQEBAQICAgCQ3SSoqVTMzTBwo
|
||||
OB0mOR0pORsoOBAgQBwoOB0qOhwpOR4oOxgoOBsqNR4pORwoOh0pOR0pOR4qOh0qOSAoOB0pOR0p
|
||||
OR0xOx4qOh0pOR4pNxspOR0pOR0pOBwoOhslNR0qORwpOhwpOR0oOBwoOCAqNRslOh4qOCAwQB0p
|
||||
OB4qOR0oOhwqOR4pOR0pOB0oOiYzQB0oOB0qOiAqQBwpOR4pOSoqOR0oOBgoOBwoOBwpOSAwQBwp
|
||||
OiAgQCAoOB0pORwqOSMuOh0qOB0pOSArNSQuQB0pOCAlOiIqOx0oOBwqORslOhsoOB0pOREhQB0q
|
||||
OR4nOR4nOSAwQBwoOCAlOiArNe9PIX65Ax0pOQOk7vu2BP///6G4x94AAABcdFJOUwAAAAAAAAAA
|
||||
AEBQUGAQgJ+QXyAwb4De73/vIN+vAc/+cHC/oH8w7s/PYL8wMIAQv++fz3CwngGgTwGvjwFhIUGP
|
||||
Ec4QIb4BAWDuMQG+MAHAzjFhURGvcXABvjEw0iVpWQAAAAFiS0dEYbKwTIYAAAAJcEhZcwAAFiUA
|
||||
ABYlAUlSJPAAAAAHdElNRQfpCBUKLDZRNHdnAAAHQ0lEQVR42u2Z+X/TNhTABW1hSm3HMV7b2FlT
|
||||
nIY27QhJuccOWLuDneweY9P2//8V03t6uhyXuUkIsI/fDxDL0tPTV++QXMYaaaSRRurKnxXC2F8V
|
||||
8rotfYulgbwCaSCvQBrIK5AG8gqkgbwCaSCvQBrIK5AG8gqkgTyfXOKt+p0XgrwZBMEF5qopIY/a
|
||||
7biTLK5Jmhdw83RNPoV+B57KmdI5Zrr8rhBbYd3ei0BOhJTt2lPVlJ2uUNJeFDNH+8xjJJ8y930r
|
||||
p5l6F57pvV0h+rxu70Ug76GF15eKOCyEkXzBKBmgFoOiDHnfzrQ9vKDqy1LZjaxu70UgK0colgr5
|
||||
AFR2e73DudZeZV98DuQRvDyKowF0yy8aj2uj6Lh25wUgv09usMx8Ad7V7cCvrJhn7a5sgi4nn5Ug
|
||||
y4AXI/w1WHo8Lg8y2DZy7Eu4FUhyQ87JE92GMIgQIgtbadQqJUPI8l0NAijftMN5mrpJUOqxw3WP
|
||||
qBX69u05+cKHPJZPt+jnRIgpNQ+DyEwTSrUhaE3AdN2KP9cT07ARyh5OfZUHjzTyTF0EsgyzEzax
|
||||
+SKyOU5E6r1aRWYbpon8J4dGqm+FhznSXHHYRJUtGB4kGPt5R+9G4RVHp8dtl+Kdcck+Cxm0arIS
|
||||
mIp9rtTmO+oJdghiK2A9Mxb84C67d18WPny+TAvZItOupP7zYpA5OnFsLa8FGROgdJKDyppTeBxg
|
||||
ZUM1/LY+CCiGiX4UeUKQb+omXS0hyDqgUeeLUrqALSwFUsfY39YrnAqEzE3EytjoP7CQH5pC3UfT
|
||||
4GxHzztLgDxAm7n1vVGhZEIhWgVZ7vJh8QFjj6DopMFg4p8BbQiDBLg+HJ5DiUKMCQWRmAbBQNdd
|
||||
+f+BOJr2aAvNhoWo43olZKTTdsMak1UUpLu0mXgEFN2ieMBsxMphW8xA/hAW0o1TsASOdGsfgQVp
|
||||
kObuEW9+yJgtcPbca9/X2KsgizzTy8E+kBJMfvBCmKnKGqlW1Smmw8JI16wR7afukeXK+WmKOyrf
|
||||
FpWQs5wYmstIoc2L1dZzYT0opgqvsoWGvP4xJAY55tIn8sdjtvFEKv30VL44kz8+Y4vKJkajmt11
|
||||
h2twuNe7MAs501GQm5XbPdId/cfMuOeYsnRu9qJQ1E2PPeX8TGcL7dAVkFk20JGtCkNCI9Q8AaUL
|
||||
6qzzxUh5KEH+PNYO+0UPPBwcuY9zAPX+wueuAZnM3aOoypbKHSohn6heMiCLDOVL9wyY+bocT55a
|
||||
f8qwpN1WwwdKpenBDWQN1+aLMmTZe6ATeYfcZqjU7qIdVhkz+QJZGsiQkZ86CgH6llLxFeZuJX9X
|
||||
CGP/VIjP2J6AJu7V1fHWSsiUcYUnQ1epky64hUxpNcCw4d7ovBJyoptsvpiFDLaNeqiF+4UbB3Ph
|
||||
3xdlChljVjCQv5ZL+sbR9rBwVfT1Ds0LeeRZZEx5ZIHUh2yzjZ/gIfYf0BHuopD3vD7ZuZAZpY3i
|
||||
vyB/iw+blA4I8neTVwrZ02ZifN+N95dAlrZ9HxjxD23cmyPzMrW6W0Aaic3oViXk3LPv5ssgY/ba
|
||||
Vu+tUbwEWaV/uR/PmO/Jz0qQ89mFzQk58X2RjmFQ9PJTB/KJWbkPeRcr/6xsmqpJwXKL9qiwmzDE
|
||||
mPWvwWXIV3/w7TuZhXzfKQawmypMvE3wIUO+gCV0HMiYk1GLugJCTn7KZmROyLp2g5jzhVP0iKQ6
|
||||
1Q5mIMdmX44D7txGxuCAdIjHC0eHIG+b+p8rzUR91OJhCTIE6b2BLTsYwnwG8p4wx7Nkgtswti0p
|
||||
qvUhw+sdrYIgw8e4fkfP8oyt/ahPG+tJahc2J2T3asa1px045An+QcJCyNMlyFy9on0pOY+8IYRh
|
||||
hrdVJIfnZOiN18Qp4drR3acOZDw4BSqMtUe98xPB8yEDM7EDNvCchsPtEO+L+2ohPmTKkGoFBHnj
|
||||
idyfp0O66N1Vz1tS6cbPv+jD3NyQE+Fezeja9siJz1saGMjhDGTcgLwdtbv+vpSqj0o96sbXLXrQ
|
||||
WUUKBEkeR4UuarhCCxmvBndJIy58ewYyzZTn+KkB1cLNSBRRXFDAlCCrWtpxIbMrv0Jb0Ybth+/L
|
||||
eOMTvSgGrY8X8+Q9j02kcmRRgowkBXxWm4Ws3wn3wofym1VypNI7DiflXfUhJzN17Qbc/H4vQXaz
|
||||
hQKij2huzrUmiFypPZ6YBn0FcCCP8W3oQcaEQZa0/Gfx3FSn+SDvevZSvpiBzPBrw1HGKiCz0aEC
|
||||
OfM3nIxuCOYNQh7j9exIfygfqw8Z3edoRRmymy3oyhtXnC643rno1MztNpQg4wKnduPUV7g1Wshz
|
||||
Un31D/qS55TmeY9wNeX4+PTcd5msx9Vvjzl3xlEgnPq9r77g/MX5ymsKfPvOyi21/6ykZD3zLdk4
|
||||
Let4xZCXIaUvGm+hNJBXIA3kFcibD3n9rIH8ymX9rCiK0evmtJC8+ZD/B9JAXoE0kFcgDeQVSAO5
|
||||
kUYaaeQNkX8BdcAtjNRFbWoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjUtMDgtMjFUMTA6NDQ6NTMr
|
||||
MDA6MDBp1auWAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDI1LTA4LTIxVDEwOjQ0OjUzKzAwOjAwGIgT
|
||||
KgAAACh0RVh0ZGF0ZTp0aW1lc3RhbXAAMjAyNS0wOC0yMVQxMDo0NDo1NCswMDowMIo6DHsAAAAA
|
||||
SUVORK5CYII=" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg width="21" height="22" viewBox="0 0 21 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Microsoft">
|
||||
<rect id="Rectangle 1010" y="0.5" width="10" height="10" fill="#EF4F21"/>
|
||||
<rect id="Rectangle 1012" y="11.5" width="10" height="10" fill="#03A4EE"/>
|
||||
<rect id="Rectangle 1011" x="11" y="0.5" width="10" height="10" fill="#7EB903"/>
|
||||
<rect id="Rectangle 1013" x="11" y="11.5" width="10" height="10" fill="#FBB604"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 439 B |
@@ -0,0 +1,40 @@
|
||||
provider: azure_openai
|
||||
label:
|
||||
en_US: Azure OpenAI
|
||||
icon_small:
|
||||
en_US: icon_s_en.svg
|
||||
icon_large:
|
||||
en_US: icon_l_en.svg
|
||||
background: "#E3F0FF"
|
||||
help:
|
||||
title:
|
||||
en_US: Get your API key from Azure
|
||||
zh_Hans: 从 Azure 获取 API Key
|
||||
url:
|
||||
en_US: https://azure.microsoft.com/en-us/products/ai-services/openai-service
|
||||
supported_model_types:
|
||||
- llm
|
||||
configurate_methods:
|
||||
- customizable-model
|
||||
provider_credential_schema:
|
||||
credential_form_schemas:
|
||||
- variable: api_key
|
||||
label:
|
||||
en_US: API Key
|
||||
type: secret-input
|
||||
required: true
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的 API Key
|
||||
en_US: Enter your API Key
|
||||
- variable: base_url
|
||||
type: text-input
|
||||
required: true
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的 Base URL
|
||||
en_US: Enter your Base URL
|
||||
- variable: api_version
|
||||
label:
|
||||
en_US: '2024-02-01'
|
||||
type: text-input
|
||||
required: true
|
||||
address: https://docs-test-001.openai.azure.com/
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+124
-155
@@ -6,6 +6,8 @@ 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"
|
||||
@@ -13,7 +15,6 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -22,12 +23,80 @@ var _ IMcpController = (*imlMcpController)(nil)
|
||||
type imlMcpController struct {
|
||||
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 = `{
|
||||
"mcpServers": {
|
||||
"%s": {
|
||||
@@ -45,151 +114,18 @@ func (i *imlMcpController) GlobalMCPConfig(ctx *gin.Context) (string, error) {
|
||||
return fmt.Sprintf(mcpDefaultConfig, "APIPark-MCP-Server", fmt.Sprintf("%s/openapi/v1/%s/sse?apikey={your_api_key}", strings.TrimSuffix(cfg.SitePrefix, "/"), mcp_server.GlobalBasePath)), nil
|
||||
}
|
||||
|
||||
func (i *imlMcpController) generateZhCNMCPServer() *server.MCPServer {
|
||||
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"service_list",
|
||||
mcp2.WithDescription("此工具用于获取 APIPark 中已注册服务的列表。每个服务包含其唯一标识(service ID)、名称、描述及包含的 API 列表等关键信息。支持通过关键词进行模糊搜索,以便快速缩小查找范围。在获得某个服务的 ID 后,可以调用 openapi_document 工具来获取该服务的 OpenAPI 文档,以便后续调用其提供的 API 接口。"),
|
||||
mcp2.WithString("keyword", mcp2.Description("关键词,用于模糊搜索服务")),
|
||||
),
|
||||
i.mcpModule.Services,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"openapi_document",
|
||||
mcp2.WithDescription("此工具用于获取指定服务的 OpenAPI 接口文档。返回内容支持 OpenAPI v3 与 v2 两种规范格式。通过传入服务 ID,可以查看该服务的所有 API 定义、参数结构、请求方式等详细信息,为后续构造请求做准备。"),
|
||||
mcp2.WithString("service", mcp2.Description("服务的唯一标识 ID")),
|
||||
),
|
||||
i.mcpModule.APIs,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"invoke_api",
|
||||
mcp2.WithDescription("此工具用于直接调用指定的 API 接口。调用前需根据该接口的 OpenAPI 文档构造必要的请求参数,如请求路径、方法、查询参数、请求头、请求体等。调用过程中无需传递认证信息,例如请求头中的 Authorization 字段不需要提供。"),
|
||||
mcp2.WithString("path", mcp2.Description("API 请求路径"), mcp2.Required()),
|
||||
mcp2.WithString("method", mcp2.Description("API 请求方法,例如 GET、POST、PUT"), mcp2.Required()),
|
||||
mcp2.WithString("content-type", mcp2.Description("请求的 Content-Type 类型。如果方法为 POST、PUT 或 PATCH,则必须指定该字段。")),
|
||||
mcp2.WithObject("query", mcp2.Description("请求的查询参数,类型为 map[string]string")),
|
||||
mcp2.WithObject("header", mcp2.Description("请求的头部参数,类型为 map[string]string")),
|
||||
mcp2.WithString("body", mcp2.Description("请求体内容,通常为 JSON 字符串")),
|
||||
),
|
||||
i.mcpModule.Invoke,
|
||||
)
|
||||
return s
|
||||
}
|
||||
|
||||
func (i *imlMcpController) generateZhTWMCPServer() *server.MCPServer {
|
||||
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"service_list",
|
||||
mcp2.WithDescription("此工具用於獲取 APIPark 中已註冊服務的清單。每個服務包含其唯一識別碼(service ID)、名稱、描述以及該服務所包含的 API 列表。支援關鍵字模糊搜尋,可快速縮小查詢範圍。獲取到服務 ID 後,可使用 openapi_document 工具來查詢該服務對應的 OpenAPI 文件,為後續 API 呼叫做準備。"),
|
||||
mcp2.WithString("keyword", mcp2.Description("關鍵字,用於模糊搜尋服務")),
|
||||
),
|
||||
i.mcpModule.Services,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"openapi_document",
|
||||
mcp2.WithDescription("此工具用於查詢指定服務的 OpenAPI 文件。返回的格式支援 OpenAPI v3 與 v2 標準。透過輸入服務 ID,可查閱該服務所有 API 的定義、參數結構、請求方式等細節,有助於後續構造 API 呼叫請求。"),
|
||||
mcp2.WithString("service", mcp2.Description("欲查詢的服務唯一識別碼")),
|
||||
),
|
||||
i.mcpModule.APIs,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"invoke_api",
|
||||
mcp2.WithDescription("此工具可直接發送 API 請求。在呼叫此工具之前,需根據該 API 的 OpenAPI 文件構造所需的請求參數,如請求路徑、方法、查詢參數、標頭、主體等。使用此工具時不需傳送任何認證資訊,例如 Authorization 標頭可省略。"),
|
||||
mcp2.WithString("path", mcp2.Description("API 的請求路徑"), mcp2.Required()),
|
||||
mcp2.WithString("method", mcp2.Description("API 的請求方法,例如 GET、POST、PUT"), mcp2.Required()),
|
||||
mcp2.WithString("content-type", mcp2.Description("請求的 Content-Type。若方法為 POST、PUT 或 PATCH,則必須指定")),
|
||||
mcp2.WithObject("query", mcp2.Description("請求的查詢參數,類型為 map[string]string")),
|
||||
mcp2.WithObject("header", mcp2.Description("請求的標頭,類型為 map[string]string")),
|
||||
mcp2.WithString("body", mcp2.Description("請求主體內容,通常為 JSON 字串")),
|
||||
),
|
||||
i.mcpModule.Invoke,
|
||||
)
|
||||
return s
|
||||
}
|
||||
|
||||
func (i *imlMcpController) generateEnMCPServer() *server.MCPServer {
|
||||
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"service_list",
|
||||
mcp2.WithDescription("This tool can retrieve a list of registered services on APIPark, including key information such as service ID, name, description, and API list within the service. Support keyword search to quickly narrow down the search scope. After obtaining the service ID, you can use this ID to call the tool openapi_document to obtain the openapi document of the service for the corresponding service, preparing for subsequent API calls."),
|
||||
mcp2.WithString("keyword", mcp2.Description("Keyword for fuzzy search")),
|
||||
),
|
||||
i.mcpModule.Services,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"openapi_document",
|
||||
mcp2.WithDescription("This tool returns the openAPI documentation for the corresponding service. The format supports the specifications of OpenAPI v3 and OpenAPI v2."),
|
||||
mcp2.WithString("service", mcp2.Description("Service ID")),
|
||||
),
|
||||
i.mcpModule.APIs,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"invoke_api",
|
||||
mcp2.WithDescription("This tool can directly make API calls. Before calling this tool, it is necessary to construct relevant parameters based on the corresponding API's openAPI documentation, including query, header, body, method, path, and other parameters. By using this tool, no authentication related information needs to be transmitted, that is, no request header Authorization needs to be transmitted."),
|
||||
mcp2.WithString("path", mcp2.Description("API path"), mcp2.Required()),
|
||||
mcp2.WithString("method", mcp2.Description("API method"), mcp2.Required()),
|
||||
mcp2.WithString("content-type", mcp2.Description("API Request Content-Type. If method is POST,PUT,PATCH, it must be set. If not set, it will be ignored.")),
|
||||
mcp2.WithObject("query", mcp2.Description("API Request query,param type is map[string]string")),
|
||||
mcp2.WithObject("header", mcp2.Description("API Request header,param type is map[string]string")),
|
||||
mcp2.WithString("body", mcp2.Description("API Request body")),
|
||||
),
|
||||
i.mcpModule.Invoke,
|
||||
)
|
||||
return s
|
||||
}
|
||||
|
||||
func (i *imlMcpController) generateJPMCPServer() *server.MCPServer {
|
||||
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"service_list",
|
||||
mcp2.WithDescription("このツールは、APIPark に登録されているサービスの一覧を取得するためのものです。各サービスには、サービスID、名称、説明、およびそのサービスに含まれるAPI一覧といった重要な情報が含まれます。キーワードによるあいまい検索が可能で、目的のサービスを素早く絞り込むことができます。取得したサービスIDを使用して openapi_document ツールを呼び出すことで、そのサービスの OpenAPI ドキュメントを取得でき、APIの利用準備が整います。"),
|
||||
mcp2.WithString("keyword", mcp2.Description("キーワード。サービスをあいまい検索するための文字列")),
|
||||
),
|
||||
i.mcpModule.Services,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"openapi_document",
|
||||
mcp2.WithDescription("指定されたサービスの OpenAPI ドキュメントを取得するためのツールです。OpenAPI v3 および v2 のフォーマットに対応しています。このドキュメントを使用することで、APIのエンドポイント、リクエスト方法、パラメータなどの詳細を確認でき、API呼び出しの準備に役立ちます。"),
|
||||
mcp2.WithString("service", mcp2.Description("対象のサービスID")),
|
||||
),
|
||||
i.mcpModule.APIs,
|
||||
)
|
||||
s.AddTool(
|
||||
mcp2.NewTool(
|
||||
"invoke_api",
|
||||
mcp2.WithDescription("このツールは、指定された API を直接呼び出すためのものです。呼び出し前に、OpenAPI ドキュメントに基づいて必要なパラメータ(パス、メソッド、クエリ、ヘッダー、ボディなど)を構築する必要があります。呼び出し時に認証情報(例:Authorization ヘッダー)を送信する必要はありません。"),
|
||||
mcp2.WithString("path", mcp2.Description("API のリクエストパス"), mcp2.Required()),
|
||||
mcp2.WithString("method", mcp2.Description("HTTPメソッド(GET、POST、PUTなど)。"), mcp2.Required()),
|
||||
mcp2.WithString("content-type", mcp2.Description("リクエストの Content-Type。メソッドが POST、PUT、PATCH の場合に必須。")),
|
||||
mcp2.WithObject("query", mcp2.Description("リクエストのクエリパラメータ。型は map[string]string")),
|
||||
mcp2.WithObject("header", mcp2.Description("リクエストヘッダー。型は map[string]string")),
|
||||
mcp2.WithString("body", mcp2.Description("リクエストボディ。通常はJSON文字列")),
|
||||
),
|
||||
i.mcpModule.Invoke,
|
||||
)
|
||||
return s
|
||||
}
|
||||
|
||||
func (i *imlMcpController) OnComplete() {
|
||||
i.server = make(map[string]http.Handler)
|
||||
enSer := i.generateEnMCPServer()
|
||||
i.server["en-US"] = server.NewSSEServer(enSer, server.WithBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
||||
i.server["zh-CN"] = server.NewSSEServer(i.generateZhCNMCPServer(), server.WithBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
||||
i.server["zh-TW"] = server.NewSSEServer(i.generateZhTWMCPServer(), server.WithBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
||||
i.server["ja-JP"] = server.NewSSEServer(i.generateJPMCPServer(), server.WithBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
||||
|
||||
i.openServer = server.NewSSEServer(enSer, server.WithBasePath(fmt.Sprintf("/openapi/v1/%s", strings.Trim(mcp_server.GlobalBasePath, "/"))))
|
||||
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) {
|
||||
@@ -200,15 +136,17 @@ func (i *imlMcpController) GlobalMCPHandle(ctx *gin.Context) {
|
||||
v.ServeHTTP(ctx.Writer, req)
|
||||
return
|
||||
}
|
||||
i.server["en-US"].ServeHTTP(ctx.Writer, req)
|
||||
i.server[languageEnUs].ServeHTTP(ctx.Writer, req)
|
||||
}
|
||||
|
||||
func (i *imlMcpController) GlobalHandleSSE(ctx *gin.Context) {
|
||||
apikey := ctx.Request.URL.Query().Get("apikey")
|
||||
i.handleSSE(ctx, i.openServer, apikey)
|
||||
i.handleSSE(ctx, i.openServer, SessionInfo{
|
||||
Apikey: apikey,
|
||||
})
|
||||
}
|
||||
|
||||
func (i *imlMcpController) handleSSE(ctx *gin.Context, server http.Handler, apikey string) {
|
||||
func (i *imlMcpController) handleSSE(ctx *gin.Context, server http.Handler, sIn SessionInfo) {
|
||||
|
||||
writer := &ResponseWriter{
|
||||
Writer: ctx.Writer,
|
||||
@@ -222,7 +160,7 @@ func (i *imlMcpController) handleSSE(ctx *gin.Context, server http.Handler, apik
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
i.sessionKeys.Store(sessionId, apikey)
|
||||
i.sessionKeys.Store(sessionId, sIn)
|
||||
}()
|
||||
server.ServeHTTP(writer, ctx.Request)
|
||||
i.sessionKeys.Delete(sessionId)
|
||||
@@ -246,7 +184,7 @@ func (i *imlMcpController) ServiceHandleSSE(ctx *gin.Context) {
|
||||
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid service id", "success": "fail"})
|
||||
return
|
||||
}
|
||||
ok, err := i.authorizationModule.CheckAPIKeyAuthorization(ctx, serviceId, apikey)
|
||||
ok, err := i.authorizationModule.CheckAPIKeyAuthorizationByService(ctx, serviceId, apikey)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": err.Error(), "success": "fail"})
|
||||
return
|
||||
@@ -256,22 +194,53 @@ func (i *imlMcpController) ServiceHandleSSE(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
i.handleSSE(ctx, mcp_server.DefaultMCPServer(), apikey)
|
||||
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 {
|
||||
|
||||
+208
-18
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -16,7 +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) ([]*system_apikey_dto.AuthorizationItem, error)
|
||||
MyAPIKeysByService(ctx *gin.Context, serviceId string, appId string) ([]*system_apikey_dto.AuthorizationItem, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -12,8 +12,14 @@ type imlAPIKeyController struct {
|
||||
apikeyModule system_apikey.IAPIKeyModule `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlAPIKeyController) MyAPIKeysByService(ctx *gin.Context, serviceId string) ([]*system_apikey_dto.AuthorizationItem, error) {
|
||||
return i.apikeyModule.MyAPIKeysByService(ctx, serviceId)
|
||||
func (i *imlAPIKeyController) 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) {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"plugins": ["react", "@typescript-eslint", "prettier", "unused-imports"],
|
||||
"rules": {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"prettier/prettier": "error",
|
||||
"prettier/prettier": "off",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
.next/
|
||||
node_modules
|
||||
dist
|
||||
market_dist
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const srcDir = path.join(__dirname, 'src');
|
||||
|
||||
const filesToDelete = [
|
||||
'package.json',
|
||||
'tsconfig.json',
|
||||
'tsconfig.node.json',
|
||||
'vite.config.ts',
|
||||
'postcss.config.js',
|
||||
'tailwind.config.js',
|
||||
'.eslintrc.cjs',
|
||||
'index.html',
|
||||
'start-vite.js'
|
||||
];
|
||||
|
||||
function walkDirAndClean(dir) {
|
||||
if (!fs.existsSync(dir)) return;
|
||||
|
||||
fs.readdirSync(dir).forEach(f => {
|
||||
let dirPath = path.join(dir, f);
|
||||
try {
|
||||
let stat = fs.statSync(dirPath);
|
||||
if (stat.isDirectory() && f !== 'node_modules') {
|
||||
// If it's a top-level module directory like src/core, src/common
|
||||
if (dir === srcDir) {
|
||||
filesToDelete.forEach(file => {
|
||||
const fileToDelete = path.join(dirPath, file);
|
||||
if (fs.existsSync(fileToDelete)) {
|
||||
fs.unlinkSync(fileToDelete);
|
||||
console.log(`Deleted: ${fileToDelete}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
});
|
||||
}
|
||||
|
||||
walkDirAndClean(srcDir);
|
||||
console.log('Cleanup completed!');
|
||||
@@ -0,0 +1,50 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const srcDir = path.join(__dirname, 'src');
|
||||
|
||||
function walkDir(dir, callback) {
|
||||
if (dir.includes('node_modules')) return;
|
||||
fs.readdirSync(dir).forEach(f => {
|
||||
let dirPath = path.join(dir, f);
|
||||
try {
|
||||
let isDirectory = fs.statSync(dirPath).isDirectory();
|
||||
isDirectory ? walkDir(dirPath, callback) : callback(path.join(dir, f));
|
||||
} catch(e) {}
|
||||
});
|
||||
}
|
||||
|
||||
const filesToRename = [];
|
||||
const filesToUpdate = [];
|
||||
|
||||
walkDir(srcDir, (filePath) => {
|
||||
if (filePath.endsWith('.module.css')) {
|
||||
filesToRename.push(filePath);
|
||||
} else if (filePath.endsWith('.tsx') || filePath.endsWith('.ts')) {
|
||||
filesToUpdate.push(filePath);
|
||||
}
|
||||
});
|
||||
|
||||
filesToRename.forEach(oldPath => {
|
||||
const newPath = oldPath.replace(/\.module\.css$/, '.css');
|
||||
|
||||
// Read and remove :global wrappers entirely
|
||||
let content = fs.readFileSync(oldPath, 'utf8');
|
||||
content = content.replace(/:global\(([^)]+)\)/g, '$1'); // replace :global(.foo) with .foo
|
||||
content = content.replace(/:global\s+/g, ''); // replace :global .foo with .foo
|
||||
|
||||
fs.writeFileSync(oldPath, content);
|
||||
fs.renameSync(oldPath, newPath);
|
||||
console.log(`Renamed and cleaned: ${path.basename(oldPath)} -> ${path.basename(newPath)}`);
|
||||
});
|
||||
|
||||
filesToUpdate.forEach(filePath => {
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
if (content.includes('.module.css')) {
|
||||
content = content.replace(/\.module\.css/g, '.css');
|
||||
fs.writeFileSync(filePath, content);
|
||||
console.log(`Updated imports in: ${path.basename(filePath)}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Done!');
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "independent"
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const rootDir = __dirname;
|
||||
const packagesDir = path.join(rootDir, 'packages');
|
||||
const srcDir = path.join(rootDir, 'src');
|
||||
const appDir = path.join(srcDir, 'app');
|
||||
|
||||
console.log('🚀 开始拆除 Lerna 并迁移至 Next.js...');
|
||||
|
||||
// 1. 创建基础目录
|
||||
if (!fs.existsSync(srcDir)) fs.mkdirSync(srcDir);
|
||||
if (!fs.existsSync(appDir)) fs.mkdirSync(appDir);
|
||||
|
||||
// 2. 读取并合并 package.json
|
||||
const rootPkgPath = path.join(rootDir, 'package.json');
|
||||
const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, 'utf8'));
|
||||
|
||||
const mergedDeps = { ...rootPkg.dependencies };
|
||||
const mergedDevDeps = { ...rootPkg.devDependencies };
|
||||
|
||||
if (fs.existsSync(packagesDir)) {
|
||||
const packages = fs.readdirSync(packagesDir);
|
||||
for (const pkg of packages) {
|
||||
const pkgPath = path.join(packagesDir, pkg);
|
||||
if (fs.statSync(pkgPath).isDirectory()) {
|
||||
// 合并依赖
|
||||
const childPkgPath = path.join(pkgPath, 'package.json');
|
||||
if (fs.existsSync(childPkgPath)) {
|
||||
const childPkg = JSON.parse(fs.readFileSync(childPkgPath, 'utf8'));
|
||||
Object.assign(mergedDeps, childPkg.dependencies || {});
|
||||
Object.assign(mergedDevDeps, childPkg.devDependencies || {});
|
||||
}
|
||||
// 移动目录到 src 下
|
||||
const destPath = path.join(srcDir, pkg);
|
||||
if (!fs.existsSync(destPath)) {
|
||||
fs.renameSync(pkgPath, destPath);
|
||||
console.log(`📦 已迁移模块: packages/${pkg} -> src/${pkg}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 删除空的 packages 文件夹
|
||||
try { fs.rmdirSync(packagesDir); } catch (e) { console.error('Failed to remove packages dir, skipping', e) }
|
||||
}
|
||||
|
||||
// 3. 清理并更新根 package.json
|
||||
delete rootPkg.workspaces; // 移除 lerna workspaces
|
||||
rootPkg.scripts = {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
};
|
||||
|
||||
// 移除 Vite 和 Lerna 相关依赖
|
||||
const removeDeps = ['lerna', 'vite', '@originjs/vite-plugin-federation', '@vitejs/plugin-react', 'vite-tsconfig-paths'];
|
||||
removeDeps.forEach(dep => {
|
||||
delete mergedDeps[dep];
|
||||
delete mergedDevDeps[dep];
|
||||
});
|
||||
|
||||
// 添加 Next.js 和 React 最新核心依赖 (与 xroute-ui 对齐)
|
||||
mergedDeps['next'] = "15.4.5";
|
||||
mergedDeps['react'] = "19.1.0";
|
||||
mergedDeps['react-dom'] = "19.1.0";
|
||||
mergedDevDeps['@types/react'] = "^19";
|
||||
mergedDevDeps['@types/react-dom'] = "^19";
|
||||
|
||||
rootPkg.dependencies = mergedDeps;
|
||||
rootPkg.devDependencies = mergedDevDeps;
|
||||
|
||||
fs.writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2));
|
||||
console.log('✅ package.json 依赖已合并并重写');
|
||||
|
||||
// 4. 生成 tsconfig.json (配置路径别名)
|
||||
const tsconfigPath = path.join(rootDir, 'tsconfig.json');
|
||||
const tsconfig = {
|
||||
compilerOptions: {
|
||||
target: "es5",
|
||||
lib: ["dom", "dom.iterable", "esnext"],
|
||||
allowJs: true,
|
||||
skipLibCheck: true,
|
||||
strict: false,
|
||||
noEmit: true,
|
||||
esModuleInterop: true,
|
||||
module: "esnext",
|
||||
moduleResolution: "bundler",
|
||||
resolveJsonModule: true,
|
||||
isolatedModules: true,
|
||||
jsx: "preserve",
|
||||
incremental: true,
|
||||
plugins: [{ name: "next" }],
|
||||
baseUrl: ".",
|
||||
paths: {
|
||||
"@/*": ["src/*"],
|
||||
// 欺骗原有代码,使其能找到拍平后的新路径
|
||||
"@apipark/common/*": ["src/common/src/*"],
|
||||
"@apipark/core/*": ["src/core/src/*"],
|
||||
"@apipark/dashboard/*": ["src/dashboard/src/*"],
|
||||
"@apipark/market/*": ["src/market/src/*"],
|
||||
"@apipark/openApi/*": ["src/openApi/src/*"],
|
||||
"@apipark/systemRunning/*": ["src/systemRunning/src/*"]
|
||||
}
|
||||
},
|
||||
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
exclude: ["node_modules"]
|
||||
};
|
||||
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
|
||||
console.log('✅ tsconfig.json 别名映射已配置');
|
||||
|
||||
// 5. 创建 Next.js App Router 挂载点
|
||||
const layoutCode = `export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}`;
|
||||
fs.writeFileSync(path.join(appDir, 'layout.tsx'), layoutCode);
|
||||
|
||||
const slugDir = path.join(appDir, '[[...slug]]');
|
||||
if (!fs.existsSync(slugDir)) fs.mkdirSync(slugDir, { recursive: true });
|
||||
|
||||
const pageCode = `"use client";
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
// 动态导入原有的 Vite SPA 根组件,禁用 SSR 避免 window 报错
|
||||
const ApiParkApp = dynamic(() => import('@/core/src/App'), {
|
||||
ssr: false,
|
||||
loading: () => <div style={{ padding: 50 }}>Loading APIPark...</div>
|
||||
});
|
||||
|
||||
export default function Page() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
useEffect(() => setMounted(true), []);
|
||||
|
||||
if (!mounted) return null;
|
||||
return <ApiParkApp />;
|
||||
}`;
|
||||
fs.writeFileSync(path.join(slugDir, 'page.tsx'), pageCode);
|
||||
|
||||
console.log('✅ Next.js 路由挂载点创建完毕!');
|
||||
console.log('🎉 迁移完成!请执行 pnpm install 重新安装依赖。');
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
/// <reference types="next" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
@@ -0,0 +1,42 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
images: {
|
||||
disableStaticImages: true,
|
||||
},
|
||||
experimental: {
|
||||
optimizePackageImports: ["@heroui/react"],
|
||||
},
|
||||
transpilePackages: ['@heroui/react', '@heroui/theme', '@ant-design', 'antd', 'rc-util', 'rc-pagination', 'rc-picker', 'rc-tree', 'rc-table'],
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/api/v1/:path*',
|
||||
destination: 'http://172.18.166.219:8288/api/v1/:path*', // Proxy to backend
|
||||
},
|
||||
{
|
||||
source: '/api2/v1/:path*',
|
||||
destination: 'http://172.18.166.219:8288/api2/v1/:path*', // Proxy to backend 2
|
||||
}
|
||||
];
|
||||
},
|
||||
webpack: (config) => {
|
||||
config.module.rules.push({
|
||||
test: /\.(svg|png|jpe?g|gif|webp)$/i,
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
filename: 'static/media/[name].[hash][ext]'
|
||||
}
|
||||
});
|
||||
|
||||
// 解决一些 Node.js polyfill 在浏览器端缺失的问题
|
||||
config.resolve.fallback = {
|
||||
...config.resolve.fallback,
|
||||
fs: false,
|
||||
path: false,
|
||||
os: false,
|
||||
};
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
+59
-29
@@ -2,20 +2,12 @@
|
||||
"name": "frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "set NODE_OPTIONS=--max-old-space-size=4096 && 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",
|
||||
"stop": "kill-port --port 5000",
|
||||
"scan": "i18next-scanner --config i18next-scanner.config.js",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
|
||||
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix && prettier --write ."
|
||||
"dev": "next dev -p 5000",
|
||||
"build": "next build",
|
||||
"start": "next start -p 5000",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -23,50 +15,88 @@
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@ant-design/pro-components": "2.7.19",
|
||||
"@originjs/vite-plugin-federation": "^1.3.3",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@floating-ui/react": "^0.26.24",
|
||||
"@formkit/auto-animate": "^0.8.1",
|
||||
"@heroui/react": "^3.0.3",
|
||||
"@heroui/styles": "^3.0.3",
|
||||
"@heroui/theme": "^2.4.20",
|
||||
"@lexical/code": "^0.17.1",
|
||||
"@lexical/react": "^0.17.1",
|
||||
"@lexical/selection": "^0.17.1",
|
||||
"@lexical/text": "^0.17.1",
|
||||
"@lexical/utils": "^0.17.1",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"@mui/icons-material": "^5.15.6",
|
||||
"@mui/lab": "5.0.0-alpha.150",
|
||||
"@mui/material": "5.14.14",
|
||||
"@mui/x-data-grid-pro": "6.18.1",
|
||||
"@rollup/plugin-dynamic-import-vars": "^2.1.2",
|
||||
"@tinymce/tinymce-react": "^4.3.2",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"@xyflow/react": "^12.3.6",
|
||||
"ahooks": "^3.8.1",
|
||||
"allotment": "^1.20.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"crc": "^4.3.2",
|
||||
"dayjs": "^1.11.10",
|
||||
"dompurify": "^3.1.6",
|
||||
"echarts": "^5.5.0",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"framer-motion": "^10.16.4",
|
||||
"fs-extra": "^11.2.0",
|
||||
"highlight.js": "^11.9.0",
|
||||
"i18next": "^23.12.2",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"js-base64": "^3.7.5",
|
||||
"lexical": "^0.17.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"next": "15.4.5",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-nesting": "^12.1.5",
|
||||
"react": "^18.2.0",
|
||||
"rc-picker": "^4.1.1",
|
||||
"react": "19.1.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hook-form": "^7.49.3",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-joyride": "^2.8.2",
|
||||
"react-router-dom": "6.20.0",
|
||||
"swagger-ui-react": "^5.17.14",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"uuid": "^9.0.1",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"react-json-view": "^1.21.3",
|
||||
"zod": "^3.23.8",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0"
|
||||
"react-router-dom": "6.20.0",
|
||||
"react-virtuoso": "^4.7.11",
|
||||
"swagger-ui-react": "^5.17.14",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"tinymce": "^6.8.1",
|
||||
"use-context-selector": "^2.0.0",
|
||||
"uuid": "^9.0.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/cssinjs": "^1.18.2",
|
||||
"@antv/g6": "^4.8.24",
|
||||
"@formily/antd-v5": "^1.2.1",
|
||||
"@formily/core": "^2.2.13",
|
||||
"@formily/react": "^2.2.13",
|
||||
"@formily/reactive": "^2.2.13",
|
||||
"@iconify/react": "^5.0.2",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@tailwindcss/postcss": "^4.2.1",
|
||||
"lightningcss": "^1.32.0",
|
||||
"@testing-library/jest-dom": "^6.4.5",
|
||||
"@testing-library/react": "^15.0.7",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.10.5",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"antd": "^5.19.4",
|
||||
"babel-jest": "^29.7.0",
|
||||
"eslint": "^8.53.0",
|
||||
@@ -76,22 +106,22 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"exceljs": "^4.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next-scanner": "^4.5.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jsdom": "^24.0.0",
|
||||
"lerna": "^8.1.3",
|
||||
"less": "^4.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"monaco-editor": "^0.45.0",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"prettier": "^3.1.1",
|
||||
"react-test-renderer": "^18.3.1",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.0",
|
||||
"vite-jest": "^0.1.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "common",
|
||||
"version": "1.0.0",
|
||||
"description": "Common library for AO Platform",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"test": "node ./__tests__/common.test.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.26.24",
|
||||
"@formkit/auto-animate": "^0.8.1",
|
||||
"@lexical/code": "^0.17.1",
|
||||
"@lexical/react": "^0.17.1",
|
||||
"@lexical/selection": "^0.17.1",
|
||||
"@lexical/text": "^0.17.1",
|
||||
"@lexical/utils": "^0.17.1",
|
||||
"@mui/icons-material": "^5.15.6",
|
||||
"@mui/lab": "5.0.0-alpha.150",
|
||||
"@mui/material": "5.14.14",
|
||||
"@mui/x-data-grid-pro": "6.18.1",
|
||||
"ahooks": "^3.8.1",
|
||||
"allotment": "^1.20.0",
|
||||
"echarts": "^5.5.0",
|
||||
"lexical": "^0.17.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"rc-picker": "^4.1.1",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hook-form": "^7.49.3",
|
||||
"use-context-selector": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formily/antd-v5": "^1.2.1",
|
||||
"@formily/core": "^2.2.13",
|
||||
"@formily/react": "^2.2.13",
|
||||
"@formily/reactive": "^2.2.13",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"exceljs": "^4.4.0",
|
||||
"monaco-editor": "^0.45.0"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
export default {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
'tailwindcss/nesting': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"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": "输入名称查找用户"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import * as monaco from 'monaco-editor'
|
||||
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
|
||||
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
|
||||
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
|
||||
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
|
||||
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
|
||||
|
||||
self.MonacoEnvironment = {
|
||||
getWorker(_, label) {
|
||||
if (label === 'json') {
|
||||
return new jsonWorker()
|
||||
}
|
||||
if (label === 'css' || label === 'scss' || label === 'less') {
|
||||
return new cssWorker()
|
||||
}
|
||||
if (label === 'html' || label === 'handlebars' || label === 'razor') {
|
||||
return new htmlWorker()
|
||||
}
|
||||
if (label === 'typescript' || label === 'javascript') {
|
||||
return new tsWorker()
|
||||
}
|
||||
return new editorWorker()
|
||||
}
|
||||
}
|
||||
|
||||
export { monaco }
|
||||
@@ -1,98 +0,0 @@
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
important:true,
|
||||
content: [
|
||||
`../*/src/**/*.{js,ts,jsx,tsx}`,
|
||||
]
|
||||
,
|
||||
theme: {
|
||||
extend: {
|
||||
width: {
|
||||
INPUT_NORMAL: '100%',
|
||||
// INPUT_NORMAL: '346px',
|
||||
INPUT_LARGE: '508px',
|
||||
GROUP: '240px',
|
||||
SEARCH: '276px',
|
||||
LOG: '254px'
|
||||
},
|
||||
minHeight:{
|
||||
TEXTAREA:'68px'
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: 'var(--border-radius)',
|
||||
SEARCH_RADIUS: '50px'
|
||||
},
|
||||
boxShadow:{
|
||||
SCROLL: '0 2px 2px #0000000d',
|
||||
SCROLL_TOP:' 0 -2px 2px -2px var(--border-color)'
|
||||
},
|
||||
colors: {
|
||||
DISABLE_BG: 'var(--disabled-background-color)',
|
||||
MAIN_TEXT: 'var(--text-color)',
|
||||
MAIN_HOVER_TEXT: 'var(--text-hover-color)',
|
||||
SECOND_TEXT:'var(--disabled-text-color)',
|
||||
MAIN_BG: 'var(--background-color)',
|
||||
MENU_BG:'var(--MENU-BG-COLOR)',
|
||||
'bar-theme': 'var(--bar-background-color)',
|
||||
BORDER: 'var(--border-color)',
|
||||
NAVBAR_BTN_BG: 'var(--item-active-background-color)',
|
||||
MAIN_DISABLED_BG: 'var(--disabled-background-color)',
|
||||
theme: 'var(--primary-color)',
|
||||
DESC_TEXT: 'var(--TITLE_TEXT)',
|
||||
HOVER_BG: 'var(--item-hover-background-color)',
|
||||
guide_cluster: '#ee6760',
|
||||
guide_upstream: '#f9a429',
|
||||
guide_api: '#71d24d',
|
||||
guide_publishApi: '#5884ff',
|
||||
guide_final: '#915bf9',
|
||||
table_text: 'var(--table-text-color)',
|
||||
status_success:'#138913',
|
||||
status_fail:"#ff3b30",
|
||||
status_update:"#03a9f4",
|
||||
status_pending:"#ffa500",
|
||||
status_offline:"#8f8e93",
|
||||
A_HOVER:'var(--button-primary-hover-background-color)'
|
||||
},
|
||||
backgroundImage:{
|
||||
LAYOUT_BG:'linear-gradient(107.97deg, rgba(32,41,117,1) 4.41%,rgba(16,13,27,1) 86.11%)',
|
||||
LAYOUT_BG_DARK:'#fff',
|
||||
},
|
||||
spacing: {
|
||||
mbase: 'var(--FORM_SPAN)',
|
||||
label: '12px', // 选择器和label之间的间距,待删
|
||||
btnbase: 'var(--LAYOUT_MARGIN)', // x方向的间距
|
||||
btnybase: 'var(--LAYOUT_MARGIN)', // y轴方向的间距
|
||||
btnrbase: '20px', // 页面最右侧边距20px
|
||||
formtop: 'var(--FORM_SPAN)',
|
||||
icon: '5px',
|
||||
blockbase: '40px',
|
||||
DEFAULT_BORDER_RADIUS: 'var(--border-radius)',
|
||||
TREE_TITLE:'var(--small-padding) var(--LAYOUT_PADDING);',
|
||||
'navbar-height': 'var(--layout-header-height)',
|
||||
TAG_LEFT:'10px',
|
||||
PAGE_INSIDE_X:'40px',
|
||||
PAGE_INSIDE_T:'30px',
|
||||
PAGE_INSIDE_B:'20px',
|
||||
},
|
||||
borderColor: {
|
||||
'color-base': 'var(--border-color)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
function({ addUtilities }) {
|
||||
addUtilities({
|
||||
'.h-calc-100vh-minus-navbar': {
|
||||
height: 'calc(100vh - var(--layout-header-height))',
|
||||
},
|
||||
'.w-calc-100vw-minus-padding-r': {
|
||||
width: 'calc(100% - 40px)',
|
||||
},
|
||||
}, ['responsive', 'hover']);
|
||||
}
|
||||
],
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"paths": {
|
||||
"@common/*": ["./src/*"],
|
||||
"@core/*": ["../core/src/*"],
|
||||
"@market/*": ["../market/src/*"]
|
||||
},
|
||||
},
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'path'
|
||||
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars'
|
||||
|
||||
export default defineConfig({
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
javascriptEnabled: true
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
localsConvention: 'camelCase',
|
||||
generateScopedName: '[local]_[hash:base64:2]'
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
dynamicImportVars({
|
||||
include: ['src'],
|
||||
exclude: [],
|
||||
warnOnError: false
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: [
|
||||
{ find: /^~/, replacement: '' },
|
||||
{ find: '@common', replacement: path.resolve(__dirname, './src') },
|
||||
{ find: '@market', replacement: path.resolve(__dirname, '/./market/src') },
|
||||
{ find: '@core', replacement: path.resolve(__dirname, '../core/src') }
|
||||
]
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/api/v1': {
|
||||
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
|
||||
target: 'http://172.18.166.219:8288/',
|
||||
changeOrigin: true
|
||||
},
|
||||
'/api2/v1': {
|
||||
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
|
||||
target: 'http://172.18.166.219:8288/',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
logLevel: 'info'
|
||||
})
|
||||
@@ -1 +0,0 @@
|
||||
VITE_APP_MODE=openSource
|
||||
@@ -1,18 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs','public','code-snippet','ace-editor'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head id="head">
|
||||
<meta charset="UTF-8" />
|
||||
<link id="favicon" rel="icon" type="image/svg+xml" href="/frontend/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
<body id="eo-body">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const eoBody = document.getElementById('eo-body');
|
||||
const favicon = document.getElementById('favicon');
|
||||
|
||||
const createScript = (id, src) => {
|
||||
const script = document.createElement('script');
|
||||
script.id = id;
|
||||
script.async = true;
|
||||
script.src = src;
|
||||
return script;
|
||||
};
|
||||
|
||||
const iconparkApintoSrc = window.location.hostname === 'localhost' ? '/iconpark_apinto.js' : '/frontend/iconpark_apinto.js';
|
||||
const iconparkEolinkSrc = window.location.hostname === 'localhost' ? '/iconpark_eolink.js' : '/frontend/iconpark_eolink.js';
|
||||
const faviconSrc = window.location.hostname === 'localhost' ? '/favicon.ico' : '/frontend/favicon.ico';
|
||||
|
||||
favicon.href = faviconSrc;
|
||||
|
||||
eoBody.appendChild(createScript('iconpark_apinto', iconparkApintoSrc));
|
||||
eoBody.appendChild(createScript('iconpark_eolink', iconparkEolinkSrc));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"name": "core",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": " vite --mode open --port 5000 --strictPort",
|
||||
"dev:pro": " vite --config ./vite.pro.config.ts --mode pro --port 5000 --strictPort ",
|
||||
"build": "vite build --mode open",
|
||||
"build:pro": "vite --config ./vite.pro.config.ts build --mode pro",
|
||||
"postinstall": "node scripts/moveTinymce.js",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview --port 5000 --strictPort",
|
||||
"serve": "vite preview --port 5000 --strictPort"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tinymce/tinymce-react": "^4.3.2",
|
||||
"@xyflow/react": "^12.3.6",
|
||||
"fs-extra": "^11.2.0",
|
||||
"highlight.js": "^11.9.0",
|
||||
"tinymce": "^6.8.1"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
export default {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
'tailwindcss/nesting': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,309 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.button-bottom-default {
|
||||
@apply border-[0px] border-b-[1px] border-solid border-BORDER;
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
|
||||
|
||||
#root {
|
||||
width: 100vw;
|
||||
height:100vh;
|
||||
}
|
||||
|
||||
:global.ant-tree-node-content-wrapper{
|
||||
overflow: hidden;
|
||||
}
|
||||
.tree-title-hover{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items:center;
|
||||
.tree-title-span{
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.tree-title-more{
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover .tree-title-more{
|
||||
display: flex;
|
||||
height:22px;
|
||||
width:22px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.ant-layout-content.apipark-layout-layout-content{
|
||||
border-radius:10px 0 0 0 ;
|
||||
overflow:hidden;
|
||||
background-color:'transparent'
|
||||
}
|
||||
|
||||
|
||||
.apipark-layout-global-header-collapsed-button{
|
||||
color:hsl(0, 0%, 100%);
|
||||
}
|
||||
|
||||
.apipark-layout-top-nav-header-main{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.apipark-layout-top-nav-header-menu {
|
||||
height:50px;
|
||||
line-height:50px;
|
||||
|
||||
.ant-menu-item.apipark-layout-base-menu-horizontal-menu-item.ant-menu-item-selected::after{
|
||||
border-bottom:2px solid #fff !important;
|
||||
}
|
||||
.ant-menu-item.apipark-layout-base-menu-horizontal-menu-item.ant-menu-item-active:not(.ant-menu-item-selected)::after{
|
||||
border-bottom:2px solid transparent !important;
|
||||
}
|
||||
}
|
||||
.apipark-layout-base-menu-inline-group .ant-menu-item-group-title{
|
||||
color:rgb(255 255 255 / 70%) !important;
|
||||
}
|
||||
.avatar-dom > div{
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
gap:8px;
|
||||
}
|
||||
|
||||
.apipark-layout-layout{
|
||||
|
||||
.apipark-layout-layout-bg-list{
|
||||
background-image: radial-gradient(circle farthest-corner at 450px 350px, #050eb7, #17163e 500px);
|
||||
}
|
||||
.ant-layout-header.apipark-layout-layout-header{
|
||||
backdrop-filter: unset !important;
|
||||
height:var(--layout-header-height);
|
||||
line-height: var(--layout-header-height);
|
||||
background-color: transparent;
|
||||
|
||||
li.apipark-layout-base-menu-horizontal-menu-item{
|
||||
color:rgb(255 255 255 / 70%) !important;
|
||||
|
||||
&.ant-menu-item-selected{
|
||||
color:#fff !important;
|
||||
}
|
||||
&.ant-menu-item-active{
|
||||
color:#fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
li.ant-menu-submenu-horizontal.ant-menu-overflow-item-rest .ant-menu-submenu-title{
|
||||
color:#fff !important;
|
||||
}
|
||||
}
|
||||
.ant-layout-sider.apipark-layout-sider{
|
||||
height:calc(100vh - var(--layout-header-height)) !important;
|
||||
inset-block-start: var(--layout-header-height);
|
||||
|
||||
.ant-menu {
|
||||
.ant-menu-item-group-title{
|
||||
font-size:12px;
|
||||
padding:12px 16px;
|
||||
}
|
||||
|
||||
.ant-menu-item{
|
||||
margin-block:0 !important;
|
||||
}
|
||||
.ant-menu-light:not(.ant-menu-horizontal) .ant-menu-item:not(.ant-menu-item-selected):active{
|
||||
background-color: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.apipark-layout-sider-collapsed-button{
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul.ant-menu.ant-menu-root.ant-menu-inline,
|
||||
ul.ant-menu.ant-menu-root.ant-menu-vertical{
|
||||
> li {
|
||||
color:rgb(255 255 255 / 70%) !important;
|
||||
/* border-radius: 10px;
|
||||
background-color: rgba(255,255,255,0.1) !important;
|
||||
border: 1px solid rgba(255,255,255,0.15); */
|
||||
}
|
||||
> li.ant-menu-item-active {
|
||||
color:#fff !important;
|
||||
}
|
||||
> li.ant-menu-item-selected {
|
||||
background-color: #fff !important;
|
||||
border: 1px solid #fff !important;
|
||||
color:#333 !important;
|
||||
}
|
||||
}
|
||||
ul.apipark-layout-sider-menu .ant-menu-item-group-list{
|
||||
> li {
|
||||
color:rgb(255 255 255 / 70%) !important;
|
||||
}
|
||||
> li:active{
|
||||
background-color: transparent;
|
||||
}
|
||||
> li.ant-menu-item-active {
|
||||
color:#fff !important;
|
||||
}
|
||||
> li.ant-menu-item-selected {
|
||||
background-color: #fff !important;
|
||||
border: 1px solid #fff !important;
|
||||
color:#333 !important;
|
||||
}
|
||||
}
|
||||
.ant-menu-item {
|
||||
height:40px;
|
||||
margin-block:10px;
|
||||
}
|
||||
}
|
||||
.apipark-layout-drawer-sider{
|
||||
background:#17163E;
|
||||
padding-top:20px;
|
||||
.ant-layout-sider.apipark-layout-sider{
|
||||
height: 100% !important;
|
||||
inset-block: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.apipark-layout-layout-container{
|
||||
|
||||
>.ant-layout-header{
|
||||
height:var(--layout-header-height) !important;
|
||||
line-height:var(--layout-header-height) !important;
|
||||
}
|
||||
|
||||
>.apipark-layout-layout-content.apipark-layout-layout-has-header{
|
||||
padding-block:0px;
|
||||
padding-inline:0px;
|
||||
background-color: #fff !important;
|
||||
}
|
||||
}
|
||||
.ant-pro-global-header-header-actions-avatar > div{
|
||||
color:#fff !important;
|
||||
}
|
||||
|
||||
.ant-menu-item-divider.apipark-layout-base-menu-inline-divider{
|
||||
border-color: rgb(255 255 255 / 15%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tox-tinymce{
|
||||
border:none !important;
|
||||
}
|
||||
|
||||
a{
|
||||
transition:none !important;
|
||||
}
|
||||
|
||||
.ant-result ant-result-error{
|
||||
background-color: #fff !important;
|
||||
}
|
||||
|
||||
.ant-tabs-tab-btn{
|
||||
display: flex;
|
||||
align-items:center;
|
||||
.ant-tabs-tab-icon{
|
||||
display: inline-flex;
|
||||
align-items:center;
|
||||
}
|
||||
}
|
||||
|
||||
.eo_page_list .ant-pro-table{
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
border:1px solid var(--table-border-color) !important;
|
||||
}
|
||||
|
||||
.swagger-ui{
|
||||
width: 100%;
|
||||
.model-box-control:focus,.models-control:focus, .opblock-summary-control:focus{
|
||||
outline:unset !important;
|
||||
}
|
||||
.information-container{
|
||||
.info{
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pro-table .ant-popover .ant-popover-inner-content{
|
||||
.ant-form-item{
|
||||
background-color: transparent;
|
||||
border:none;
|
||||
}
|
||||
}
|
||||
.ant-menu .ant-menu-title-content{
|
||||
display:unset !important;
|
||||
}
|
||||
|
||||
|
||||
.ai-setting-svg-container svg{
|
||||
width: 100%;
|
||||
height:100%;
|
||||
display:block;
|
||||
}
|
||||
.ai-service-api-preview .swagger-ui h3.opblock-tag{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 整个背景容器设置 */
|
||||
.background-container {
|
||||
background: radial-gradient(ellipse 80% 900px at top, rgb(255 255 255 / 10%) 0%, rgb(4 0 71) 30%, rgb(13 17 23) 100%);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
isolate: isolate;
|
||||
}
|
||||
|
||||
/* SVG背景图案 */
|
||||
.background-pattern {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
stroke: rgba(255, 255, 255, 0.1);
|
||||
mask-image: radial-gradient(100% 100% at top right, white, transparent);
|
||||
}
|
||||
|
||||
.login-block{
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
.login-input{
|
||||
color:#fff !important;
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
||||
&:hover, &:focus, &.ant-input-status-error, &.ant-input-status-error:hover, &.ant-input-status-error:focus-within{
|
||||
background: rgba(255, 255, 255, 0.2) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
||||
}
|
||||
}
|
||||
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:focus {
|
||||
transition: background-color 0s 600000s, color 0s 600000s !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select-selection-overflow-item:first-child {
|
||||
max-width: calc(100% - 60px);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
a[disabled]:hover {
|
||||
color: #BBB;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.ant-input-group-addon{
|
||||
height:32px !important;
|
||||
.ant-btn.ant-btn-default{
|
||||
height:32px !important;
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
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 {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";
|
||||
|
||||
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 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)
|
||||
}
|
||||
})
|
||||
},[])
|
||||
|
||||
|
||||
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'})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
check()
|
||||
getSystemInfo()
|
||||
}, []);
|
||||
|
||||
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>
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
</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>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
export default Login;
|
||||
@@ -1,601 +0,0 @@
|
||||
import { App, Button, Card, CascaderProps, Empty, Select } from 'antd'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||
import ReactJson from 'react-json-view'
|
||||
import { IconButton } from '@common/components/postcat/api/IconButton'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { useConnection } from './hook/useConnection'
|
||||
import { ClientRequest, Tool, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { z } from 'zod'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { ServiceDetailType } from '@market/const/serviceHub/type'
|
||||
import useCopyToClipboard from '@common/hooks/copy'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { Cascader } from 'antd/lib'
|
||||
|
||||
type ConfigList = {
|
||||
openApi?: {
|
||||
title: string
|
||||
configContent: string
|
||||
apiKeys: string[]
|
||||
}
|
||||
mcp: {
|
||||
title: string
|
||||
configContent: string
|
||||
apiKeys: string[]
|
||||
}
|
||||
}
|
||||
|
||||
type ApiKeyItem = {
|
||||
expired: number
|
||||
id: string
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
interface Option {
|
||||
value: string
|
||||
label: string
|
||||
children?: Option[]
|
||||
}
|
||||
|
||||
type ServiceApiKeyList = {
|
||||
id: string
|
||||
name: string
|
||||
apikeys: Array<{
|
||||
id: string
|
||||
name: string
|
||||
value: string
|
||||
expired: number
|
||||
}>
|
||||
}
|
||||
export interface IntegrationAIContainerRef {
|
||||
getServiceKeysList: () => void;
|
||||
}
|
||||
export interface IntegrationAIContainerProps {
|
||||
type: 'global' | 'service'
|
||||
handleToolsChange: (value: Tool[]) => void
|
||||
customClassName?: string
|
||||
service?: ServiceDetailType
|
||||
serviceId?: string
|
||||
currentTab?: string
|
||||
openModal?: (type: 'apply') => void
|
||||
}
|
||||
export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, IntegrationAIContainerProps>(
|
||||
({
|
||||
type,
|
||||
handleToolsChange,
|
||||
customClassName,
|
||||
service,
|
||||
serviceId,
|
||||
currentTab,
|
||||
openModal
|
||||
}: IntegrationAIContainerProps, ref) => {
|
||||
/** 当前激活的标签 */
|
||||
const [activeTab, setActiveTab] = useState(type === 'service' ? 'openApi' : 'mcp')
|
||||
/** 弹窗组件 */
|
||||
const { message } = App.useApp()
|
||||
/** 配置内容 */
|
||||
const [configContent, setConfigContent] = useState<string>('')
|
||||
/** 当前选中 API Key */
|
||||
const [apiKey, setApiKey] = useState<string>('')
|
||||
/** API Key 列表 */
|
||||
const [apiKeyList, setApiKeyList] = useState<any[]>([])
|
||||
/** Cascader Key 列表 */
|
||||
const [cascaderKeyList, setCascaderKeyList] = useState<string[]>([])
|
||||
/** MCP 服务器地址 */
|
||||
const [mcpServerUrl, setMcpServerUrl] = useState<string>('')
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
const navigator = useNavigate()
|
||||
/** 复制组件 */
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
/** 错误提示 */
|
||||
const [errors, setErrors] = useState<Record<string, string | null>>({
|
||||
resources: null,
|
||||
prompts: null,
|
||||
tools: null
|
||||
})
|
||||
/** 标签内容 */
|
||||
const [tabContent, setTabContent] = useState<ConfigList>({
|
||||
mcp: {
|
||||
title: $t('MCP 配置'),
|
||||
configContent: '',
|
||||
apiKeys: []
|
||||
}
|
||||
})
|
||||
/** HTTP 请求 */
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
/**
|
||||
* 初始化标签数据
|
||||
*/
|
||||
const initTabsData = () => {
|
||||
const params: ConfigList = {
|
||||
mcp: {
|
||||
title: $t('MCP 配置'),
|
||||
configContent: service?.mcpAccessConfig || '',
|
||||
apiKeys: []
|
||||
}
|
||||
}
|
||||
if (type === 'service') {
|
||||
params.openApi = {
|
||||
title: $t('Open API 文档'),
|
||||
configContent: service?.openapiAddress || '',
|
||||
apiKeys: []
|
||||
}
|
||||
}
|
||||
setTabContent(params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
const handleCopy = async (value: string): Promise<void> => {
|
||||
if (value) {
|
||||
copyToClipboard(value)
|
||||
message.success($t(RESPONSE_TIPS.copySuccess))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择 API Key
|
||||
* @param value
|
||||
*/
|
||||
const handleSelectChange = (value: string) => {
|
||||
setApiKey(value)
|
||||
}
|
||||
/**
|
||||
* Cascader 选择
|
||||
* @param value
|
||||
*/
|
||||
const handleCascaderChange: CascaderProps<Option>['onChange'] = (value) => {
|
||||
setApiKey(value.at(-1) || '')
|
||||
setCascaderKeyList(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局 MCP 配置
|
||||
* @returns
|
||||
*/
|
||||
const getGlobalMcpConfig = () => {
|
||||
fetchData<BasicResponse<null>>('global/mcp/config', {
|
||||
method: 'GET'
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setTabContent((prevTabContent) => ({
|
||||
...prevTabContent,
|
||||
mcp: {
|
||||
...prevTabContent.mcp,
|
||||
configContent: data.config || ''
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局 MCP 跳转
|
||||
*/
|
||||
const addKey = () => {
|
||||
navigator('/mcpKey')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局 API Key 列表
|
||||
*/
|
||||
const getGlobalKeysList = () => {
|
||||
fetchData<BasicResponse<null>>('simple/system/apikeys', {
|
||||
method: 'GET'
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
if (data.apikeys && data.apikeys.length > 0) {
|
||||
setApiKeyList(
|
||||
data.apikeys.map((item: ApiKeyItem) => {
|
||||
return {
|
||||
label: item.name,
|
||||
value: item.value
|
||||
}
|
||||
})
|
||||
)
|
||||
setApiKey(data.apikeys[0].value)
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出获取服务 API Key 列表
|
||||
*/
|
||||
useImperativeHandle(ref, () => ({
|
||||
getServiceKeysList
|
||||
}))
|
||||
|
||||
/**
|
||||
* 获取服务 API Key 列表
|
||||
*/
|
||||
const getServiceKeysList = () => {
|
||||
fetchData<BasicResponse<null>>(`my/app/apikeys`, {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId }
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
if (data.apps && data.apps.length > 0) {
|
||||
// 转换数据结构为 Cascader 所需格式
|
||||
const transformedData = data.apps.map((app: ServiceApiKeyList) => ({
|
||||
value: app.id,
|
||||
label: app.name,
|
||||
children: app.apikeys.map((key) => ({
|
||||
...key,
|
||||
label: key.name
|
||||
}))
|
||||
}))
|
||||
setApiKeyList(transformedData)
|
||||
if (data.apps[0].apikeys?.length) {
|
||||
setApiKey(data.apps[0].apikeys[0].value)
|
||||
setCascaderKeyList([data.apps[0].id, data.apps[0].apikeys[0].value])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除错误提示
|
||||
*/
|
||||
const clearError = (tabKey: keyof typeof errors) => {
|
||||
setErrors((prev) => ({ ...prev, [tabKey]: null }))
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求
|
||||
*/
|
||||
const makeRequest = async <T extends z.ZodType>(request: ClientRequest, schema: T, tabKey?: keyof typeof errors) => {
|
||||
try {
|
||||
const response = await makeConnectionRequest(request, schema)
|
||||
if (tabKey !== undefined) {
|
||||
clearError(tabKey)
|
||||
}
|
||||
return response
|
||||
} catch (e) {
|
||||
const errorString = (e as Error).message ?? String(e)
|
||||
if (tabKey !== undefined) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
[tabKey]: errorString
|
||||
}))
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 MCP 的 tools
|
||||
*/
|
||||
const listTools = async () => {
|
||||
const response = await makeRequest(
|
||||
{
|
||||
method: 'tools/list' as const,
|
||||
params: {}
|
||||
},
|
||||
ListToolsResultSchema,
|
||||
'tools'
|
||||
)
|
||||
handleToolsChange(response.tools)
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化连接 mcp
|
||||
*/
|
||||
const {
|
||||
connectionStatus,
|
||||
serverCapabilities,
|
||||
mcpClient,
|
||||
requestHistory,
|
||||
makeRequest: makeConnectionRequest,
|
||||
sendNotification,
|
||||
handleCompletion,
|
||||
completionsSupported,
|
||||
connect: connectMcpServer,
|
||||
disconnect: disconnectMcpServer
|
||||
} = useConnection({
|
||||
transportType: 'sse',
|
||||
sseUrl: '',
|
||||
proxyServerUrl: mcpServerUrl,
|
||||
requestTimeout: 1000
|
||||
})
|
||||
// 使用 useRef 保存最新的连接状态和断开函数
|
||||
const connectionStatusRef = useRef(connectionStatus)
|
||||
const disconnectFnRef = useRef(disconnectMcpServer)
|
||||
|
||||
// 当连接状态或断开函数变化时更新 ref
|
||||
useEffect(() => {
|
||||
connectionStatusRef.current = connectionStatus
|
||||
disconnectFnRef.current = disconnectMcpServer
|
||||
}, [connectionStatus, disconnectMcpServer])
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
const setupComponent = () => {
|
||||
initTabsData()
|
||||
if (type === 'global') {
|
||||
getGlobalMcpConfig()
|
||||
setMcpServerUrl('mcp/global/sse')
|
||||
getGlobalKeysList()
|
||||
} else {
|
||||
service?.basic.enableMcp && setMcpServerUrl(`mcp/service/${serviceId}/sse`)
|
||||
getServiceKeysList()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
useEffect(() => {
|
||||
setupComponent()
|
||||
}, [service])
|
||||
/**
|
||||
* 初始化标签数据
|
||||
*/
|
||||
useEffect(() => {
|
||||
initTabsData()
|
||||
type === 'global' && getGlobalMcpConfig()
|
||||
}, [state.language])
|
||||
/**
|
||||
* 切换标签
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (type === 'service') {
|
||||
currentTab === 'MCP' ? setActiveTab('mcp') : setActiveTab('openApi')
|
||||
}
|
||||
}, [currentTab])
|
||||
/**
|
||||
* 仅在组件加载时执行初始化逻辑
|
||||
*/
|
||||
useEffect(() => {
|
||||
// 返回清理函数,只会在组件卸载时执行
|
||||
return () => {
|
||||
try {
|
||||
// 使用 ref 中保存的最新函数强制断开连接
|
||||
const disconnectFn = disconnectFnRef.current
|
||||
if (disconnectFn) {
|
||||
disconnectFn()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('断开连接时出错:', err)
|
||||
}
|
||||
}
|
||||
}, [type])
|
||||
/**
|
||||
* 切换标签时更新配置内容
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (activeTab === 'openApi' && tabContent?.openApi?.configContent) {
|
||||
setConfigContent(tabContent?.openApi?.configContent)
|
||||
} else if (activeTab === 'mcp' && tabContent?.mcp?.configContent) {
|
||||
setConfigContent(tabContent.mcp.configContent?.replace('{your_api_key}', apiKey || '{your_api_key}'))
|
||||
}
|
||||
}, [service, apiKey, activeTab, tabContent])
|
||||
/**
|
||||
* 连接 MCP 服务器
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (mcpServerUrl) {
|
||||
if (connectionStatus === 'connected') {
|
||||
disconnectMcpServer()
|
||||
}
|
||||
connectMcpServer()
|
||||
}
|
||||
}, [mcpServerUrl, ...(type === 'global' ? [state.language] : [])])
|
||||
/**
|
||||
* 获取 MCP tools
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (connectionStatus === 'connected') {
|
||||
listTools()
|
||||
}
|
||||
}, [connectionStatus])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
style={{ borderRadius: '10px' }}
|
||||
className={`w-[400px] h-fit ${customClassName}`}
|
||||
classNames={{
|
||||
body: 'p-[10px]'
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
<Icon
|
||||
icon="icon-park-solid:connection-point-two"
|
||||
className="align-text-bottom mr-[5px]"
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
{$t('AI 代理集成')}
|
||||
</p>
|
||||
{type === 'service' && service?.basic.enableMcp && (
|
||||
<div className="mt-3 tab-nav flex rounded-md overflow-hidden border border-solid border-[#3D46F2] w-fit">
|
||||
<div
|
||||
className={`tab-item px-5 py-1.5 cursor-pointer text-sm transition-colors ${activeTab === 'openApi' ? 'bg-[#3D46F2] text-white' : 'bg-white text-[#3D46F2]'}`}
|
||||
onClick={() => setActiveTab('openApi')}
|
||||
>
|
||||
Open API
|
||||
</div>
|
||||
<div
|
||||
className={`tab-item px-5 py-1.5 cursor-pointer text-sm transition-colors ${activeTab === 'mcp' ? 'bg-[#3D46F2] text-white' : 'bg-white text-[#3D46F2]'}`}
|
||||
onClick={() => setActiveTab('mcp')}
|
||||
>
|
||||
MCP
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{type === 'service' && !apiKeyList.length ? (
|
||||
<>
|
||||
<Card
|
||||
style={{ borderRadius: '10px' }}
|
||||
className={`w-full mt-3`}
|
||||
classNames={{
|
||||
body: 'p-[10px]'
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center py-3">
|
||||
<span className="text-[14px] mb-5">{$t('请先订阅该服务')}</span>
|
||||
<Button type="primary" onClick={() => openModal?.('apply')}>
|
||||
{$t('申请')}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="tab-container mt-3">
|
||||
<div className="tab-content font-semibold mt-[10px]">
|
||||
{activeTab === 'openApi' ? tabContent.openApi?.title : tabContent.mcp.title}
|
||||
</div>
|
||||
{/* 标签页内容区域 */}
|
||||
<div className="bg-[#0a0b21] text-white p-4 rounded-md my-2 font-mono text-sm overflow-auto relative">
|
||||
{activeTab === 'mcp' ? (
|
||||
<ReactJson
|
||||
src={
|
||||
configContent
|
||||
? typeof configContent === 'string'
|
||||
? (() => {
|
||||
try {
|
||||
return JSON.parse(configContent)
|
||||
} catch (e) {
|
||||
return {}
|
||||
}
|
||||
})()
|
||||
: configContent
|
||||
: {}
|
||||
}
|
||||
theme="monokai"
|
||||
indentWidth={2}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
name={false}
|
||||
collapsed={false}
|
||||
enableClipboard={false}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<pre className="whitespace-pre-wrap break-words">{configContent || ''}</pre>
|
||||
</>
|
||||
)}
|
||||
<IconButton
|
||||
name="copy"
|
||||
onClick={() => handleCopy(configContent)}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '5px',
|
||||
right: '5px',
|
||||
color: '#999',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
</div>
|
||||
{activeTab === 'mcp' && (
|
||||
<>
|
||||
<div className="tab-content font-semibold my-[10px]">API Key</div>
|
||||
{apiKeyList.length ? (
|
||||
<>
|
||||
{type === 'global' ? (
|
||||
<>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
value={apiKey}
|
||||
className="w-full"
|
||||
onChange={handleSelectChange}
|
||||
options={apiKeyList}
|
||||
/>
|
||||
<Card
|
||||
style={{ borderRadius: '5px' }}
|
||||
className="w-full mt-[5px] "
|
||||
classNames={{
|
||||
body: 'p-[5px]'
|
||||
}}
|
||||
>
|
||||
<div className="relative h-[25px]">
|
||||
{apiKey}
|
||||
<IconButton
|
||||
name="copy"
|
||||
onClick={() => handleCopy(apiKey)}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
right: '5px',
|
||||
color: '#999',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Cascader
|
||||
className='w-full'
|
||||
allowClear={false}
|
||||
options={apiKeyList}
|
||||
value={cascaderKeyList}
|
||||
onChange={handleCascaderChange}
|
||||
placeholder={$t('选择 API Key')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={''}>
|
||||
<Button onClick={addKey} type="primary">
|
||||
{$t('新增 API Key')}
|
||||
</Button>
|
||||
</Empty>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
})
|
||||
@@ -1,199 +0,0 @@
|
||||
|
||||
import PageList from "@common/components/aoplatform/PageList.tsx"
|
||||
import {ActionType} from "@ant-design/pro-components";
|
||||
import {FC, useEffect, useMemo, useRef, useState} from "react";
|
||||
import {useLocation, useNavigate} from "react-router-dom";
|
||||
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {App, Modal} from "antd";
|
||||
import {BasicResponse, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import { SimpleMemberItem } from "@common/const/type.ts";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import { TEAM_TABLE_COLUMNS } from "../../const/team/const.tsx";
|
||||
import { TeamConfigFieldType, TeamConfigHandle, TeamTableListItem } from "../../const/team/type.ts";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
|
||||
import { checkAccess } from "@common/utils/permission.ts";
|
||||
import TeamConfig from "./TeamConfig.tsx";
|
||||
import InsidePage from "@common/components/aoplatform/InsidePage.tsx";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
|
||||
const TeamList:FC = ()=>{
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation()
|
||||
const currentUrl = location.pathname
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal,message } = App.useApp()
|
||||
const pageListRef = useRef<ActionType>(null);
|
||||
const {fetchData} = useFetch()
|
||||
const [memberValueEnum, setMemberValueEnum] = useState<{[k:string]:{text:string}}>({})
|
||||
const teamConfigRef = useRef<TeamConfigHandle>(null)
|
||||
const {accessData,checkPermission,accessInit, getGlobalAccessData,state} = useGlobalContext()
|
||||
const [curTeam, setCurTeam] = useState<TeamConfigFieldType>({} as TeamConfigFieldType)
|
||||
const [modalVisible, setModalVisible] = useState<boolean>(false)
|
||||
const [modalType, setModalType] = useState<'add'|'edit'>('add')
|
||||
const getTeamList = ()=>{
|
||||
if(!accessInit){
|
||||
getGlobalAccessData()?.then?.(()=>{getTeamList()})
|
||||
return
|
||||
}
|
||||
return fetchData<BasicResponse<{teams:TeamTableListItem}>>(!checkPermission('system.workspace.team.view_all') ? 'teams':'manager/teams',{method:'GET',eoParams:{keyword:searchWord},eoTransformKeys:['create_time','service_num','can_delete']}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
return {data:data.teams, success: true}
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return {data:[], success:false}
|
||||
}
|
||||
}).catch(() => {
|
||||
return {data:[], success:false}
|
||||
})
|
||||
}
|
||||
|
||||
const deleteTeam = (entity:TeamTableListItem)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
fetchData<BasicResponse<null>>(`manager/team`,{method:'DELETE',eoParams:{id:entity.id}}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
const getMemberList = async ()=>{
|
||||
setMemberValueEnum({})
|
||||
const {code,data,msg} = await fetchData<BasicResponse<{ members: SimpleMemberItem[] }>>('simple/member',{method:'GET'})
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
const tmpValueEnum:{[k:string]:{text:string}} = {}
|
||||
data.members?.forEach((x:SimpleMemberItem)=>{
|
||||
tmpValueEnum[x.name] = {text:x.name}
|
||||
})
|
||||
setMemberValueEnum(tmpValueEnum)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}
|
||||
|
||||
const manualReloadTable = () => {
|
||||
pageListRef.current?.reload()
|
||||
};
|
||||
|
||||
const openModal = async (type:'add'|'edit'|'delete',entity?:TeamTableListItem)=>{
|
||||
let title:string = ''
|
||||
let content:string | React.ReactNode= ''
|
||||
switch (type){
|
||||
case 'add':{
|
||||
setModalType('add')
|
||||
setModalVisible(true)
|
||||
return;}
|
||||
case 'edit':{
|
||||
message.loading($t(RESPONSE_TIPS.loading))
|
||||
const {code,data,msg} = await fetchData<BasicResponse<{team:TeamConfigFieldType}>>(`manager/team`,{method:'GET',eoParams:{id:entity!.id}})
|
||||
message.destroy()
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setCurTeam({...data.team,master:data.team.master.id})
|
||||
setModalVisible(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return
|
||||
}
|
||||
setModalType('edit')
|
||||
return;}
|
||||
case 'delete':
|
||||
title=$t('删除')
|
||||
content=$t(DELETE_TIPS.default)
|
||||
break;
|
||||
}
|
||||
|
||||
modal.confirm({
|
||||
title,
|
||||
content,
|
||||
onOk:()=>{
|
||||
switch (type){
|
||||
case 'delete':
|
||||
return deleteTeam(entity!).then((res)=>{if(res === true) manualReloadTable()})
|
||||
}
|
||||
},
|
||||
width:600,
|
||||
okText:$t('确认'),
|
||||
okButtonProps:{
|
||||
disabled : !checkAccess( `system.organization.team.${type}`, accessData)
|
||||
},
|
||||
cancelText:$t('取消'),
|
||||
closable:true,
|
||||
icon:<></>,
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{title: $t('团队')}
|
||||
])
|
||||
manualReloadTable()
|
||||
}, [currentUrl]);
|
||||
|
||||
useEffect(()=>{
|
||||
getMemberList()
|
||||
},[])
|
||||
|
||||
const columns = useMemo(()=>{
|
||||
return TEAM_TABLE_COLUMNS.map(x=>{if(x.filters &&((x.dataIndex as string[])?.indexOf('master') !== -1 ) ){x.valueEnum = memberValueEnum} return {...x, title:typeof x.title === 'string' ? $t(x.title as string) : x.title}})
|
||||
},[memberValueEnum,state.language])
|
||||
|
||||
|
||||
return (
|
||||
<InsidePage
|
||||
pageTitle={$t('团队')}
|
||||
description={$t("设置团队和成员,然后你可以在团队内创建服务和消费者、订阅API,成员只能看到所属团队内的服务和消费者。")}
|
||||
showBorder={false}
|
||||
contentClassName=" pr-PAGE_INSIDE_X pb-PAGE_INSIDE_B"
|
||||
>
|
||||
<PageList
|
||||
id="global_team"
|
||||
className="pl-btnbase"
|
||||
ref={pageListRef}
|
||||
columns = {[...columns]}
|
||||
request = {()=>getTeamList()}
|
||||
showPagination={false}
|
||||
addNewBtnTitle={$t('添加团队')}
|
||||
addNewBtnAccess = "system.organization.team.add"
|
||||
searchPlaceholder={$t("输入名称、ID、负责人查找团队")}
|
||||
onAddNewBtnClick={()=>{openModal('add')}}
|
||||
onSearchWordChange={(e)=>{setSearchWord(e.target.value)}}
|
||||
onRowClick={(row:TeamTableListItem)=>(navigate(`../inside/${row.id}/setting`))}
|
||||
/>
|
||||
<Modal
|
||||
title={modalType === 'add' ? $t("添加团队") : $t("配置团队")}
|
||||
open={modalVisible}
|
||||
width={600}
|
||||
destroyOnClose={true}
|
||||
maskClosable={false}
|
||||
afterOpenChange={(open:boolean)=>{
|
||||
if(!open){
|
||||
setModalVisible(false)
|
||||
setCurTeam({} as unknown as TeamConfigFieldType)
|
||||
}
|
||||
}}
|
||||
onCancel={() => {setModalVisible(false)}}
|
||||
okText={$t("确认")}
|
||||
okButtonProps={{disabled : !checkAccess( `system.organization.team.edit`, accessData)}}
|
||||
cancelText={$t('取消')}
|
||||
closable={true}
|
||||
onOk={()=>teamConfigRef.current?.save().then((res)=>{
|
||||
if(res){
|
||||
setModalVisible(false)
|
||||
manualReloadTable()
|
||||
}
|
||||
return res})}
|
||||
>
|
||||
<TeamConfig ref={teamConfigRef} entity={modalType === 'add' ? undefined : curTeam} />
|
||||
</Modal>
|
||||
</InsidePage>
|
||||
)
|
||||
|
||||
}
|
||||
export default TeamList
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
// start-vite.js// start-vite.js
|
||||
import { exec } from 'child_process';
|
||||
|
||||
const viteProcess = exec('pnpm run build');
|
||||
|
||||
viteProcess.stdout.on('data', (data) => {
|
||||
console.log(data.toString());
|
||||
});
|
||||
|
||||
viteProcess.stderr.on('data', (data) => {
|
||||
console.error(data.toString());
|
||||
});
|
||||
|
||||
viteProcess.on('close', (code) => {
|
||||
console.log(`Vite process exited with code ${code}`);
|
||||
});
|
||||
@@ -1,82 +0,0 @@
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
export default {
|
||||
important:true,
|
||||
content: [
|
||||
`./index.html`,
|
||||
`../*/src/**/*.{js,ts,jsx,tsx}`,
|
||||
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
width: {
|
||||
INPUT_NORMAL: '100%',
|
||||
// INPUT_NORMAL: '346px',
|
||||
INPUT_LARGE: '508px',
|
||||
GROUP: '240px',
|
||||
SEARCH: '276px',
|
||||
LOG: '254px'
|
||||
},
|
||||
minHeight:{
|
||||
TEXTAREA:'68px'
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: 'var(--border-radius)',
|
||||
SEARCH_RADIUS: '50px'
|
||||
},
|
||||
boxShadow:{
|
||||
SCROLL: '0 2px 2px #0000000d',
|
||||
SCROLL_TOP:' 0 -2px 2px -2px var(--border-color)'
|
||||
},
|
||||
colors: {
|
||||
DISABLE_BG: 'var(--disabled-background-color)',
|
||||
MAIN_TEXT: 'var(--text-color)',
|
||||
MAIN_HOVER_TEXT: 'var(--text-hover-color)',
|
||||
SECOND_TEXT:'var(--disabled-text-color)',
|
||||
MAIN_BG: 'var(--background-color)',
|
||||
MENU_BG:'var(--MENU-BG-COLOR)',
|
||||
'bar-theme': 'var(--bar-background-color)',
|
||||
BORDER: 'var(--border-color)',
|
||||
NAVBAR_BTN_BG: 'var(--item-active-background-color)',
|
||||
MAIN_DISABLED_BG: 'var(--disabled-background-color)',
|
||||
theme: 'var(--primary-color)',
|
||||
DESC_TEXT: 'var(--TITLE_TEXT)',
|
||||
HOVER_BG: 'var(--item-hover-background-color)',
|
||||
guide_cluster: '#ee6760',
|
||||
guide_upstream: '#f9a429',
|
||||
guide_api: '#71d24d',
|
||||
guide_publishApi: '#5884ff',
|
||||
guide_final: '#915bf9',
|
||||
table_text: 'var(--table-text-color)',
|
||||
status_success:'#138913',
|
||||
status_fail:"#ff3b30",
|
||||
status_update:"#03a9f4",
|
||||
status_pending:"#ffa500",
|
||||
status_offline:"#8f8e93",
|
||||
A_HOVER:'var(--button-primary-hover-background-color)'
|
||||
},
|
||||
spacing: {
|
||||
mbase: 'var(--FORM_SPAN)',
|
||||
label: '12px', // 选择器和label之间的间距,待删
|
||||
btnbase: 'var(--LAYOUT_MARGIN)', // x方向的间距
|
||||
btnybase: 'var(--LAYOUT_MARGIN)', // y轴方向的间距
|
||||
btnrbase: '20px', // 页面最右侧边距20px
|
||||
formtop: 'var(--FORM_SPAN)',
|
||||
icon: '5px',
|
||||
blockbase: '40px',
|
||||
DEFAULT_BORDER_RADIUS: 'var(--border-radius)',
|
||||
TREE_TITLE:'var(--small-padding) var(--LAYOUT_PADDING);',
|
||||
},
|
||||
borderColor: {
|
||||
'color-base': 'var(--border-color)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [],
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"typeRoots": ["./node_modules/@types", "../common/src/types"],
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"paths": {
|
||||
"@core/*": ["./src/*"],
|
||||
"@common/*": ["../common/src/*"],
|
||||
"@market/*": ["../market/src/*"],
|
||||
"@dashboard/*": ["../dashboard/src/*"],
|
||||
},
|
||||
},
|
||||
"include": ["src", "public/iconpark_eolink.js", "public/iconpark_apinto.js", "../common/src/component/aoplatform/EditableTableWithModal.tsx", "../common/src/components/aoplatform/TreeWithMore.tsx", "../common/src/components/aoplatform/DatePicker.tsx", "../common/src/components/aoplatform/TimeRangeSelector.tsx", "../common/src/components/aoplatform/TimePicker.tsx", "../common/src/components/aoplatform/MemberTransfer.tsx", "../common/src/components/aoplatform/PageList.tsx", "../common/src/components/aoplatform/ErrorBoundary.tsx", "../common/src/components/aoplatform/ScrollableSection.tsx", "../common/src/utils/postcat.tsx", "../common/src/utils/curl.ts", "../common/src/components/aoplatform/ResetPsw.tsx", "../common/src/components/aoplatform/SubscribeApprovalModalContent.tsx", "../common/src/components/aoplatform/InsidePageForHub.tsx", "src/components/aoplatform/RenderRoutes.tsx", "../common/src/components/aoplatform/PublishApprovalModalContent.tsx", "../common/src/components/aoplatform/InsidePage.tsx", "../common/src/const/type.ts", "../common/src/components/aoplatform/intelligent-plugin", "../common/src/const/domain"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'path'
|
||||
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars';
|
||||
import tailwindcss from 'tailwindcss';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import federation from "@originjs/vite-plugin-federation";
|
||||
|
||||
export default defineConfig({
|
||||
cacheDir: './node_modules/.vite',
|
||||
build:{
|
||||
target: 'esnext',
|
||||
outDir:'../../dist',
|
||||
sourcemap: false,
|
||||
chunkSizeWarningLimit: 50,
|
||||
cacheDir: './node_modules/.vite',
|
||||
rollupOptions: {
|
||||
output: {
|
||||
chunkFileNames: 'assets/eo-[name]-[hash].js',
|
||||
},
|
||||
},
|
||||
},
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
tailwindcss(path.resolve(__dirname, '../common/tailwind.config.js')),
|
||||
autoprefixer
|
||||
],
|
||||
},
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
modules:{
|
||||
localsConvention:"camelCase",
|
||||
generateScopedName:"[local]_[hash:base64:2]"
|
||||
}
|
||||
},
|
||||
plugins: [react(),
|
||||
dynamicImportVars({
|
||||
include:["src"],
|
||||
exclude:[],
|
||||
warnOnError:false
|
||||
}),
|
||||
federation({
|
||||
name:"container",
|
||||
remotes:{
|
||||
remoteApp: 'http://localhost:5001/assets/remoteEntry.js' // 远程项目的URL
|
||||
},
|
||||
shared:[
|
||||
"react",
|
||||
"react-dom",
|
||||
]
|
||||
})
|
||||
|
||||
],
|
||||
resolve: {
|
||||
alias: [
|
||||
{ find: /^~/, replacement: '' },
|
||||
{ find: '@common', replacement: path.resolve(__dirname, '../common/src') },
|
||||
{ find: '@market', replacement: path.resolve(__dirname, '../market/src') },
|
||||
{ find: '@core', replacement: path.resolve(__dirname, './src') },
|
||||
{ find: '@dashboard', replacement: path.resolve(__dirname, '../dashboard/src') },
|
||||
]
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/api/v1': {
|
||||
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
|
||||
target: 'http://172.18.166.219:8288/',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/api2/v1': {
|
||||
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
|
||||
target: 'http://172.18.166.219:8288/',
|
||||
changeOrigin: true,
|
||||
}
|
||||
},
|
||||
open: true
|
||||
},
|
||||
logLevel:'info'
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "dashboard",
|
||||
"version": "0.0.0",
|
||||
"description": "dashboard for AO Platform",
|
||||
"author": "maggieyyy ",
|
||||
"homepage": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"echarts-for-react": "^3.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
export default {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
'tailwindcss/nesting': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import MonitorTotalPage from '@dashboard/component/MonitorTotalPage'
|
||||
import { BasicResponse } from '@common/const/const'
|
||||
import {
|
||||
InvokeData,
|
||||
MessageData,
|
||||
MonitorApiData,
|
||||
MonitorSubscriberData,
|
||||
PieData,
|
||||
SearchBody
|
||||
} from '@dashboard/const/type'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { objectToSearchParameters } from '@common/utils/router'
|
||||
export default function DashboardTotal() {
|
||||
const { fetchData } = useFetch()
|
||||
const navigateTo = useNavigate()
|
||||
const fetchPieData: (body: SearchBody) => Promise<BasicResponse<PieData>> = (body: SearchBody) =>
|
||||
fetchData<BasicResponse<PieData>>('monitor/overview/summary', {
|
||||
method: 'POST',
|
||||
eoBody: body,
|
||||
eoTransformKeys: ['request_summary', 'proxy_summary']
|
||||
})
|
||||
|
||||
const fetchInvokeData: (body: SearchBody) => Promise<BasicResponse<InvokeData>> = (body: SearchBody) =>
|
||||
fetchData<BasicResponse<InvokeData>>('monitor/overview/invoke', {
|
||||
method: 'POST',
|
||||
eoBody: body,
|
||||
eoTransformKeys: ['request_total', 'request_rate', 'proxy_total', 'proxy_rate', 'time_interval']
|
||||
})
|
||||
|
||||
const fetchMessageData: (body: SearchBody) => Promise<BasicResponse<MessageData>> = (body: SearchBody) =>
|
||||
fetchData<BasicResponse<MessageData>>('monitor/overview/message', {
|
||||
method: 'POST',
|
||||
eoBody: body,
|
||||
eoTransformKeys: ['time_interval', 'request_message', 'response_message']
|
||||
})
|
||||
|
||||
const fetchTableData: (
|
||||
body: SearchBody,
|
||||
type: 'api' | 'subscribers' | 'providers'
|
||||
) => Promise<BasicResponse<{ top10: MonitorApiData[] | MonitorSubscriberData[] }>> = (
|
||||
body: SearchBody,
|
||||
type: 'api' | 'subscribers' | 'providers'
|
||||
) =>
|
||||
fetchData<BasicResponse<{ api: MonitorApiData[]; subscribers: MonitorSubscriberData }>>('monitor/overview/top10', {
|
||||
method: 'POST',
|
||||
eoBody: { ...body, dataType: type },
|
||||
eoTransformKeys: [
|
||||
'dataType',
|
||||
'request_total',
|
||||
'request_success',
|
||||
'request_rate',
|
||||
'proxy_total',
|
||||
'proxy_success',
|
||||
'proxy_rate',
|
||||
'status_fail',
|
||||
'avg_resp',
|
||||
'max_resp',
|
||||
'min_resp',
|
||||
'avg_traffic',
|
||||
'max_traffic',
|
||||
'min_traffic',
|
||||
'min_traffic',
|
||||
'is_red'
|
||||
]
|
||||
})
|
||||
|
||||
const goToDetail: (body: SearchBody, val: MonitorApiData | MonitorSubscriberData, type: string) => void = (
|
||||
body: SearchBody,
|
||||
val: MonitorApiData | MonitorSubscriberData,
|
||||
type: string
|
||||
) => {
|
||||
// ...跳转到详情页...
|
||||
const { start: startTime, end: endTime, clusters } = body
|
||||
navigateTo(
|
||||
`/analytics/${type}/list?${objectToSearchParameters({ id: val.id, clusters: clusters || undefined, start: startTime?.toString(), end: endTime?.toString(), name: val.name }).toString()}`
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<MonitorTotalPage
|
||||
fetchPieData={fetchPieData}
|
||||
fetchInvokeData={fetchInvokeData}
|
||||
fetchMessageData={fetchMessageData}
|
||||
fetchTableData={fetchTableData}
|
||||
goToDetail={goToDetail}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import { RangeValue } from '@common/components/aoplatform/TimeRangeSelector'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
export function getTime(
|
||||
timeButton: string,
|
||||
datePickerValue: RangeValue | [],
|
||||
init?: boolean
|
||||
): { startTime: number; endTime: number } {
|
||||
const currentSecond = new Date().getTime() // 当前毫秒数时间戳
|
||||
let currentMin = currentSecond - (currentSecond % (60 * 1000)) // 当前分钟数时间戳
|
||||
let startMin = currentMin - 60 * 60 * 1000
|
||||
if (!init && timeButton) {
|
||||
switch (timeButton) {
|
||||
case 'hour': {
|
||||
startMin = currentMin - 60 * 60 * 1000
|
||||
break
|
||||
}
|
||||
case 'day': {
|
||||
startMin = currentMin - 24 * 60 * 60 * 1000
|
||||
break
|
||||
}
|
||||
case 'threeDays': {
|
||||
startMin = new Date(new Date().setHours(0, 0, 0, 0)).getTime() - 2 * 24 * 60 * 60 * 1000
|
||||
break
|
||||
}
|
||||
case 'sevenDays': {
|
||||
startMin = new Date(new Date().setHours(0, 0, 0, 0)).getTime() - 6 * 24 * 60 * 60 * 1000
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if (datePickerValue?.length === 2) {
|
||||
startMin = datePickerValue[0]!.startOf('day').unix()
|
||||
currentMin = datePickerValue[1]!.endOf('day').unix()
|
||||
}
|
||||
|
||||
return { startTime: startMin / 1000, endTime: currentMin / 1000 }
|
||||
}
|
||||
|
||||
export function getTimeUnit(timeInterval: string): string {
|
||||
let timeUnit = ''
|
||||
// 相差秒数
|
||||
switch (timeInterval) {
|
||||
case '1m': {
|
||||
timeUnit = '每分钟'
|
||||
break
|
||||
}
|
||||
case '5m': {
|
||||
timeUnit = '每5分钟'
|
||||
break
|
||||
}
|
||||
case '1h': {
|
||||
timeUnit = '每小时'
|
||||
break
|
||||
}
|
||||
case '1d': {
|
||||
timeUnit = '每天'
|
||||
break
|
||||
}
|
||||
case '1w': {
|
||||
timeUnit = '每周'
|
||||
break
|
||||
}
|
||||
}
|
||||
return timeUnit
|
||||
}
|
||||
|
||||
// 当数据超过10万时,保留两个小数点,单位为万,如123212,显示12.32万;
|
||||
export function changeNumberUnit(value?: number): { value: string; unit: string } {
|
||||
if (value && value > 1000000000) {
|
||||
return { value: (value && value / 100000000).toFixed(2), unit: '亿' }
|
||||
} else if (value && value > 1000000) {
|
||||
return { value: (value && value / 10000).toFixed(0), unit: '万' }
|
||||
} else if (value && value > 10000) {
|
||||
return { value: (value && value / 10000).toFixed(2), unit: '万' }
|
||||
}
|
||||
return { value: (value ?? '-') + '', unit: ' 次' }
|
||||
}
|
||||
|
||||
export function yUnitFormatter(value: number): string {
|
||||
let res: string = ''
|
||||
if (value > 100000000) {
|
||||
res = (value / 100000000).toFixed(2) + $t('亿')
|
||||
} else if (value > 1000000) {
|
||||
res = (value / 10000).toFixed(0) + $t('万')
|
||||
} else if (value > 100000) {
|
||||
res = (value / 10000).toFixed(2) + $t('万')
|
||||
} else {
|
||||
res = value.toFixed(0)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
export default {
|
||||
important:true,
|
||||
content: [
|
||||
`./index.html`,
|
||||
`../*/src/**/*.{js,ts,jsx,tsx}`,
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
width: {
|
||||
INPUT_NORMAL: '100%',
|
||||
// INPUT_NORMAL: '346px',
|
||||
INPUT_LARGE: '508px',
|
||||
GROUP: '240px',
|
||||
SEARCH: '276px',
|
||||
LOG: '254px'
|
||||
},
|
||||
minHeight:{
|
||||
TEXTAREA:'68px'
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: 'var(--border-radius)',
|
||||
SEARCH_RADIUS: '50px'
|
||||
},
|
||||
boxShadow:{
|
||||
SCROLL: '0 2px 2px #0000000d',
|
||||
SCROLL_TOP:' 0 -2px 2px -2px var(--border-color)'
|
||||
},
|
||||
colors: {
|
||||
DISABLE_BG: 'var(--disabled-background-color)',
|
||||
MAIN_TEXT: 'var(--text-color)',
|
||||
MAIN_HOVER_TEXT: 'var(--text-hover-color)',
|
||||
SECOND_TEXT:'var(--disabled-text-color)',
|
||||
MAIN_BG: 'var(--background-color)',
|
||||
MENU_BG:'var(--MENU-BG-COLOR)',
|
||||
'bar-theme': 'var(--bar-background-color)',
|
||||
BORDER: 'var(--border-color)',
|
||||
NAVBAR_BTN_BG: 'var(--item-active-background-color)',
|
||||
MAIN_DISABLED_BG: 'var(--disabled-background-color)',
|
||||
theme: 'var(--primary-color)',
|
||||
DESC_TEXT: 'var(--TITLE_TEXT)',
|
||||
HOVER_BG: 'var(--item-hover-background-color)',
|
||||
guide_cluster: '#ee6760',
|
||||
guide_upstream: '#f9a429',
|
||||
guide_api: '#71d24d',
|
||||
guide_publishApi: '#5884ff',
|
||||
guide_final: '#915bf9',
|
||||
table_text: 'var(--table-text-color)',
|
||||
status_success:'#138913',
|
||||
status_fail:"#ff3b30",
|
||||
status_update:"#03a9f4",
|
||||
status_pending:"#ffa500",
|
||||
status_offline:"#8f8e93",
|
||||
A_HOVER:'var(--button-primary-hover-background-color)'
|
||||
},
|
||||
spacing: {
|
||||
mbase: 'var(--FORM_SPAN)',
|
||||
label: '12px', // 选择器和label之间的间距,待删
|
||||
btnbase: 'var(--LAYOUT_MARGIN)', // x方向的间距
|
||||
btnybase: 'var(--LAYOUT_MARGIN)', // y轴方向的间距
|
||||
btnrbase: '20px', // 页面最右侧边距20px
|
||||
formtop: 'var(--FORM_SPAN)',
|
||||
icon: '5px',
|
||||
blockbase: '40px',
|
||||
DEFAULT_BORDER_RADIUS: 'var(--border-radius)',
|
||||
TREE_TITLE:'var(--small-padding) var(--LAYOUT_PADDING);'
|
||||
},
|
||||
borderColor: {
|
||||
'color-base': 'var(--border-color)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [],
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"paths": {
|
||||
"@common/*": ["../common/src/*"],
|
||||
"@core/*": ["../core/src/*"],
|
||||
"@dashboard/*": ["./src/*"]
|
||||
},
|
||||
},
|
||||
"include": ["src", "public/iconpark_eolink.js", "public/iconpark_apinto.js", "../common/src/component/aoplatform/EditableTableWithModal.tsx", "../common/src/components/aoplatform/TreeWithMore.tsx", "../common/src/components/aoplatform/DatePicker.tsx", "../common/src/components/aoplatform/TimeRangeSelector.tsx", "../common/src/components/aoplatform/TimePicker.tsx", "../common/src/components/aoplatform/MemberTransfer.tsx", "../common/src/components/aoplatform/PageList.tsx", "../common/src/components/aoplatform/ErrorBoundary.tsx", "../core/src/pages/serviceCategory/ServiceHubCategoryConfig.tsx"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/frontend/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>APIPark - 企业API数据开放平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script src="/frontend/iconpark_eolink.js"></script>
|
||||
<script src="/frontend/iconpark_apinto.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "market",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "service market project",
|
||||
"scripts": {
|
||||
"dev": " vite --port 5000 --strictPort",
|
||||
"build": "vite build",
|
||||
"test": "node ./__tests__/market.test.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"dompurify": "^3.1.6",
|
||||
"react-virtuoso": "^4.7.11"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
export default {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
'tailwindcss/nesting': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
|
||||
#root {
|
||||
width: 100vw;
|
||||
height:100vh;
|
||||
}
|
||||
|
||||
:global.ant-tree-node-content-wrapper{
|
||||
overflow: hidden;
|
||||
}
|
||||
.tree-title-hover{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items:center;
|
||||
.tree-title-span{
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.tree-title-more{
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover .tree-title-more{
|
||||
display: flex;
|
||||
height:22px;
|
||||
width:22px;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"paths": {
|
||||
"@common/*": ["../common/src/*"],
|
||||
"@core/*": ["../core/src/*"],
|
||||
"@market/*": ["./src/*"]
|
||||
},
|
||||
},
|
||||
"include": ["src", "public/iconpark_eolink.js", "public/iconpark_apinto.js", "../common/src/component/aoplatform/EditableTableWithModal.tsx", "../common/src/components/aoplatform/TreeWithMore.tsx", "../common/src/components/aoplatform/DatePicker.tsx", "../common/src/components/aoplatform/TimeRangeSelector.tsx", "../common/src/components/aoplatform/TimePicker.tsx", "../common/src/components/aoplatform/MemberTransfer.tsx", "../common/src/components/aoplatform/PageList.tsx", "../common/src/components/aoplatform/ErrorBoundary.tsx", "../core/src/pages/serviceCategory/ServiceHubCategoryConfig.tsx"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'path'
|
||||
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars'
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
outDir: '../../tenant_dist',
|
||||
sourcemap: false,
|
||||
chunkSizeWarningLimit: 50000,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (id.includes('node_modules')) {
|
||||
return id.toString().split('node_modules/')[1].split('/')[0].toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
dynamicImportVars({
|
||||
include: ['src'],
|
||||
exclude: [],
|
||||
warnOnError: false
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: [
|
||||
{ find: /^~/, replacement: '' },
|
||||
{ find: '@market', replacement: path.resolve(__dirname, './src') },
|
||||
{ find: '@common', replacement: path.resolve(__dirname, '../common/src') },
|
||||
{ find: '@core', replacement: path.resolve(__dirname, '../core/src') }
|
||||
]
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/api/v1': {
|
||||
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
|
||||
target: 'http://172.18.166.219:8488/',
|
||||
changeOrigin: true
|
||||
},
|
||||
'/api2/v1': {
|
||||
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
|
||||
target: 'http://172.18.166.219:8488/',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
logLevel: 'info'
|
||||
})
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "open-api",
|
||||
"version": "0.0.0",
|
||||
"description": "openApi module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"test": "node ./__tests__/common.test.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"copy-to-clipboard": "^3.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
export default {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
'tailwindcss/nesting': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
export default {
|
||||
important:true,
|
||||
content: [
|
||||
`./index.html`,
|
||||
`../*/src/**/*.{js,ts,jsx,tsx}`,
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
width: {
|
||||
INPUT_NORMAL: '100%',
|
||||
// INPUT_NORMAL: '346px',
|
||||
INPUT_LARGE: '508px',
|
||||
GROUP: '240px',
|
||||
SEARCH: '276px',
|
||||
LOG: '254px'
|
||||
},
|
||||
minHeight:{
|
||||
TEXTAREA:'68px'
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: 'var(--border-radius)',
|
||||
SEARCH_RADIUS: '50px'
|
||||
},
|
||||
boxShadow:{
|
||||
SCROLL: '0 2px 2px #0000000d',
|
||||
SCROLL_TOP:' 0 -2px 2px -2px var(--border-color)'
|
||||
},
|
||||
colors: {
|
||||
DISABLE_BG: 'var(--disabled-background-color)',
|
||||
MAIN_TEXT: 'var(--text-color)',
|
||||
MAIN_HOVER_TEXT: 'var(--text-hover-color)',
|
||||
SECOND_TEXT:'var(--disabled-text-color)',
|
||||
MAIN_BG: 'var(--background-color)',
|
||||
MENU_BG:'var(--MENU-BG-COLOR)',
|
||||
'bar-theme': 'var(--bar-background-color)',
|
||||
BORDER: 'var(--border-color)',
|
||||
NAVBAR_BTN_BG: 'var(--item-active-background-color)',
|
||||
MAIN_DISABLED_BG: 'var(--disabled-background-color)',
|
||||
theme: 'var(--primary-color)',
|
||||
DESC_TEXT: 'var(--TITLE_TEXT)',
|
||||
HOVER_BG: 'var(--item-hover-background-color)',
|
||||
guide_cluster: '#ee6760',
|
||||
guide_upstream: '#f9a429',
|
||||
guide_api: '#71d24d',
|
||||
guide_publishApi: '#5884ff',
|
||||
guide_final: '#915bf9',
|
||||
table_text: 'var(--table-text-color)',
|
||||
status_success:'#138913',
|
||||
status_fail:"#ff3b30",
|
||||
status_update:"#03a9f4",
|
||||
status_pending:"#ffa500",
|
||||
status_offline:"#8f8e93",
|
||||
A_HOVER:'var(--button-primary-hover-background-color)'
|
||||
},
|
||||
spacing: {
|
||||
mbase: 'var(--FORM_SPAN)',
|
||||
label: '12px', // 选择器和label之间的间距,待删
|
||||
btnbase: 'var(--LAYOUT_MARGIN)', // x方向的间距
|
||||
btnybase: 'var(--LAYOUT_MARGIN)', // y轴方向的间距
|
||||
btnrbase: '20px', // 页面最右侧边距20px
|
||||
formtop: 'var(--FORM_SPAN)',
|
||||
icon: '5px',
|
||||
blockbase: '40px',
|
||||
DEFAULT_BORDER_RADIUS: 'var(--border-radius)',
|
||||
TREE_TITLE:'var(--small-padding) var(--LAYOUT_PADDING);'
|
||||
},
|
||||
borderColor: {
|
||||
'color-base': 'var(--border-color)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [],
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"paths": {
|
||||
"@common/*": ["../common/src/*"],
|
||||
"@core/*": ["../core/src/*"],
|
||||
"@openApi/*": ["./src/*"]
|
||||
},
|
||||
},
|
||||
"include": ["src", "public/iconpark_eolink.js", "public/iconpark_apinto.js", "../common/src/component/aoplatform/EditableTableWithModal.tsx", "../common/src/components/aoplatform/TreeWithMore.tsx", "../common/src/components/aoplatform/DatePicker.tsx", "../common/src/components/aoplatform/TimeRangeSelector.tsx", "../common/src/components/aoplatform/TimePicker.tsx", "../common/src/components/aoplatform/MemberTransfer.tsx", "../common/src/components/aoplatform/PageList.tsx", "../common/src/components/aoplatform/ErrorBoundary.tsx", "../core/src/pages/serviceCategory/ServiceHubCategoryConfig.tsx"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "systemrunning",
|
||||
"version": "1.0.0",
|
||||
"description": "> TODO: description",
|
||||
"author": "maggieyyy <61950669+maggieyyy@users.noreply.github.com>",
|
||||
"homepage": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
export default {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
'tailwindcss/nesting': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
export default {
|
||||
important:true,
|
||||
content: [
|
||||
`./index.html`,
|
||||
`../*/src/**/*.{js,ts,jsx,tsx}`,
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
width: {
|
||||
INPUT_NORMAL: '100%',
|
||||
// INPUT_NORMAL: '346px',
|
||||
INPUT_LARGE: '508px',
|
||||
GROUP: '240px',
|
||||
SEARCH: '276px',
|
||||
LOG: '254px'
|
||||
},
|
||||
minHeight:{
|
||||
TEXTAREA:'68px'
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: 'var(--border-radius)',
|
||||
SEARCH_RADIUS: '50px'
|
||||
},
|
||||
boxShadow:{
|
||||
SCROLL: '0 2px 2px #0000000d',
|
||||
SCROLL_TOP:' 0 -2px 2px -2px var(--border-color)'
|
||||
},
|
||||
colors: {
|
||||
DISABLE_BG: 'var(--disabled-background-color)',
|
||||
MAIN_TEXT: 'var(--text-color)',
|
||||
MAIN_HOVER_TEXT: 'var(--text-hover-color)',
|
||||
SECOND_TEXT:'var(--disabled-text-color)',
|
||||
MAIN_BG: 'var(--background-color)',
|
||||
MENU_BG:'var(--MENU-BG-COLOR)',
|
||||
'bar-theme': 'var(--bar-background-color)',
|
||||
BORDER: 'var(--border-color)',
|
||||
NAVBAR_BTN_BG: 'var(--item-active-background-color)',
|
||||
MAIN_DISABLED_BG: 'var(--disabled-background-color)',
|
||||
theme: 'var(--primary-color)',
|
||||
DESC_TEXT: 'var(--TITLE_TEXT)',
|
||||
HOVER_BG: 'var(--item-hover-background-color)',
|
||||
guide_cluster: '#ee6760',
|
||||
guide_upstream: '#f9a429',
|
||||
guide_api: '#71d24d',
|
||||
guide_publishApi: '#5884ff',
|
||||
guide_final: '#915bf9',
|
||||
table_text: 'var(--table-text-color)',
|
||||
status_success:'#138913',
|
||||
status_fail:"#ff3b30",
|
||||
status_update:"#03a9f4",
|
||||
status_pending:"#ffa500",
|
||||
status_offline:"#8f8e93",
|
||||
A_HOVER:'var(--button-primary-hover-background-color)'
|
||||
},
|
||||
spacing: {
|
||||
mbase: 'var(--FORM_SPAN)',
|
||||
label: '12px', // 选择器和label之间的间距,待删
|
||||
btnbase: 'var(--LAYOUT_MARGIN)', // x方向的间距
|
||||
btnybase: 'var(--LAYOUT_MARGIN)', // y轴方向的间距
|
||||
btnrbase: '20px', // 页面最右侧边距20px
|
||||
formtop: 'var(--FORM_SPAN)',
|
||||
icon: '5px',
|
||||
blockbase: '40px',
|
||||
DEFAULT_BORDER_RADIUS: 'var(--border-radius)',
|
||||
TREE_TITLE:'var(--small-padding) var(--LAYOUT_PADDING);'
|
||||
},
|
||||
borderColor: {
|
||||
'color-base': 'var(--border-color)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [],
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"paths": {
|
||||
"@common/*": ["../common/src/*"],
|
||||
"@core/*": ["../core/src/*"],
|
||||
},
|
||||
},
|
||||
"include": ["src", "public/iconpark_eolink.js", "public/iconpark_apinto.js", "../common/src/component/aoplatform/EditableTableWithModal.tsx", "../common/src/components/aoplatform/TreeWithMore.tsx", "../common/src/components/aoplatform/DatePicker.tsx", "../common/src/components/aoplatform/TimeRangeSelector.tsx", "../common/src/components/aoplatform/TimePicker.tsx", "../common/src/components/aoplatform/MemberTransfer.tsx", "../common/src/components/aoplatform/PageList.tsx", "../common/src/components/aoplatform/ErrorBoundary.tsx", "../core/src/pages/serviceCategory/ServiceHubCategoryConfig.tsx"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1,546 @@
|
||||
'use client'
|
||||
|
||||
import { BasicResponse, 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 localAIPic from '@common/assets/localAI.svg'
|
||||
import onlineAIPic from '@common/assets/onlineAI.svg'
|
||||
import restAPIPic from '@common/assets/restAPI.svg'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { checkAccess } from '@common/utils/permission'
|
||||
import AiSettingModalContent, { AiSettingModalContentHandle } from '@core/pages/aiSetting/AiSettingModal'
|
||||
import LocalAiDeploy, { LocalAiDeployHandle } from '@core/pages/guide/LocalAiDeploy'
|
||||
import RestAIDeploy, { RestAIDeployHandle } from '@core/pages/guide/RestAIDeploy'
|
||||
import useDeployLocalModel from '@core/pages/guide/deployModelUtil'
|
||||
import { App as AppAntd, Button, Card, Collapse } from 'antd'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { Dispatch, SetStateAction, useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
function AIModelGuide() {
|
||||
const { message, modal } = AppAntd.useApp()
|
||||
const entityData = useRef<any>(null)
|
||||
const router = useRouter()
|
||||
const { accessData } = useGlobalContext()
|
||||
const modalRef = useRef<AiSettingModalContentHandle>()
|
||||
const localAiDeployRef = useRef<LocalAiDeployHandle>()
|
||||
const restAiDeployRef = useRef<RestAIDeployHandle>()
|
||||
const { deployLocalModel } = useDeployLocalModel()
|
||||
const { fetchData } = useFetch()
|
||||
const [ollamaAddress, setOllamaAddress] = useState<string>('')
|
||||
|
||||
const dumpServerPage = () => {
|
||||
router.push('/service/list')
|
||||
}
|
||||
|
||||
const restCardClick = async () => {
|
||||
const permission = checkAccess('system.workspace.service.edit', accessData)
|
||||
if (!permission) {
|
||||
return message.warning($t('暂无权限'))
|
||||
}
|
||||
modal.confirm({
|
||||
title: $t('添加 Rest 服务'),
|
||||
content: <RestAIDeploy ref={restAiDeployRef}></RestAIDeploy>,
|
||||
onOk: () => {
|
||||
return restAiDeployRef.current?.deployRestAIServer().then((res) => {
|
||||
if (res === true) {
|
||||
dumpServerPage()
|
||||
}
|
||||
})
|
||||
},
|
||||
width: 600,
|
||||
okText: $t('确认'),
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
icon: <></>
|
||||
})
|
||||
}
|
||||
|
||||
const aiCardClick = () => {
|
||||
const permission = checkAccess('system.devops.ai_provider.edit', accessData)
|
||||
if (!permission) {
|
||||
return message.warning($t('暂无权限'))
|
||||
}
|
||||
const updateEntityData = (data: any) => {
|
||||
entityData.current = data
|
||||
modalInstance.update({})
|
||||
}
|
||||
const modalInstance = modal.confirm({
|
||||
title: $t('模型配置'),
|
||||
content: (
|
||||
<AiSettingModalContent
|
||||
ref={modalRef}
|
||||
modelMode="manual"
|
||||
updateEntityData={updateEntityData}
|
||||
source="guide"
|
||||
readOnly={!checkAccess('system.devops.ai_provider.edit', accessData)}
|
||||
/>
|
||||
),
|
||||
onOk: () => {
|
||||
return modalRef.current?.deployAIServer().then((res) => {
|
||||
if (res === true) {
|
||||
dumpServerPage()
|
||||
}
|
||||
})
|
||||
},
|
||||
width: 600,
|
||||
okText: $t('确认'),
|
||||
footer: (_, { OkBtn, CancelBtn }) => (
|
||||
<div className="flex justify-between items-center">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={entityData.current?.getApikeyUrl}
|
||||
className="flex items-center gap-[8px]"
|
||||
>
|
||||
<span>{$t('从 (0) 获取 API KEY', [entityData.current?.name])}</span>
|
||||
<Icon icon="ic:baseline-open-in-new" width={16} height={16} />
|
||||
</a>
|
||||
<div>
|
||||
<CancelBtn />
|
||||
{checkAccess('system.devops.ai_provider.edit', accessData) ? <OkBtn /> : null}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
icon: <></>
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchData<BasicResponse<{ data: any[] }>>('model/local/source/ollama', {
|
||||
method: 'GET'
|
||||
}).then((response) => {
|
||||
if (response.code === STATUS_CODE.SUCCESS) {
|
||||
setOllamaAddress(response.data?.config?.address || '')
|
||||
} else {
|
||||
message.error(response.msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
const localModelCardClick = async () => {
|
||||
const permission = checkAccess('system.devops.ai_provider.edit', accessData)
|
||||
if (!permission) {
|
||||
return message.warning($t('暂无权限'))
|
||||
}
|
||||
if (!ollamaAddress) {
|
||||
router.push('/aisetting?status=unconfigure')
|
||||
return
|
||||
}
|
||||
const modalInstance = modal.confirm({
|
||||
title: $t('部署本地模型'),
|
||||
content: (
|
||||
<LocalAiDeploy
|
||||
ref={localAiDeployRef}
|
||||
onClose={() => {
|
||||
modalInstance.destroy()
|
||||
dumpServerPage()
|
||||
}}
|
||||
></LocalAiDeploy>
|
||||
),
|
||||
onOk: () => {
|
||||
return localAiDeployRef.current?.deployLocalAIServer().then((res) => {
|
||||
if (res === true) {
|
||||
dumpServerPage()
|
||||
}
|
||||
})
|
||||
},
|
||||
width: 600,
|
||||
okText: $t('确认'),
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
icon: <></>
|
||||
})
|
||||
}
|
||||
|
||||
const deployDeepSeek = async (e: any) => {
|
||||
e.stopPropagation()
|
||||
const permission = checkAccess('system.devops.ai_provider.edit', accessData)
|
||||
if (!permission) {
|
||||
return message.warning($t('暂无权限'))
|
||||
}
|
||||
if (!ollamaAddress) {
|
||||
router.push('/aisetting?status=unconfigure')
|
||||
return
|
||||
}
|
||||
await deployLocalModel({ modelID: 'deepseek-r1' })
|
||||
dumpServerPage()
|
||||
}
|
||||
|
||||
const cardList = [
|
||||
{
|
||||
imgSrc: restAPIPic,
|
||||
title: $t('添加 Rest 服务'),
|
||||
description: $t('导入OpenAPI文档,将现有系统的API发布到APIPark。'),
|
||||
click: restCardClick
|
||||
},
|
||||
{
|
||||
imgSrc: onlineAIPic,
|
||||
title: $t('添加在线 AI API'),
|
||||
description: $t('添加公有云AI模型的 API Key,通过APIPark 统一调用公有云的AI模型。'),
|
||||
click: aiCardClick
|
||||
},
|
||||
{
|
||||
imgSrc: localAIPic,
|
||||
title: $t('本地部署 AI 并生成 API'),
|
||||
description: $t('快速在本地部署开源模型并自动生成 API。'),
|
||||
click: localModelCardClick,
|
||||
bottomRender: (
|
||||
<span className="text-[#2196f3] text-[13px] hover:text-[#1976d2]" onClick={deployDeepSeek}>
|
||||
<Icon className="align-sub mr-[5px]" icon="lsicon:lightning-filled" width="15" height="15" />
|
||||
{$t('部署')} Deepseek-R1
|
||||
</span>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>{$t('⚡您可快速通过以下方式开放API供大家使用:')}</p>
|
||||
<div className="mb-[30px] pt-[25px] flex justify-between space-x-4">
|
||||
{cardList.map((item, itemIndex) => (
|
||||
<Card
|
||||
key={itemIndex}
|
||||
className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] bg-[linear-gradient(153.41deg,rgba(244,245,255,1)_0.23%,rgba(255,255,255,1)_83.32%)] rounded-[10px] overflow-visible cursor-pointer flex-1 transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.05]"
|
||||
classNames={{
|
||||
header: 'border-b-[0px] p-[20px] pb-[10px] text-[14px] font-normal',
|
||||
body: 'p-[20px] pt-[50px] pb-[50px] text-[12px] text-[#666] text-center'
|
||||
}}
|
||||
onClick={item.click}
|
||||
>
|
||||
<img src={item.imgSrc} alt="" width={60} height={60} />
|
||||
<p className="text-[13px] font-bold text-black mt-[10px] mb-[10px]">{item.title}</p>
|
||||
<p className="break-words mb-[10px]">{item.description}</p>
|
||||
{item.bottomRender ? item.bottomRender : null}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function QuickGuideContent({
|
||||
changeGuideShow,
|
||||
guideSections
|
||||
}: {
|
||||
changeGuideShow: Dispatch<SetStateAction<boolean>>
|
||||
guideSections: {
|
||||
title: string
|
||||
items: {
|
||||
title: string
|
||||
description: string
|
||||
link: string
|
||||
}[]
|
||||
}[]
|
||||
}) {
|
||||
return (
|
||||
<div className="">
|
||||
{guideSections.map((section, index) => (
|
||||
<div key={index}>
|
||||
<p className="flex gap-[8px] items-center text-[14px] font-bold">
|
||||
<Icon icon="ic:baseline-info" width="18" height="18" className="text-theme" />
|
||||
{section.title}
|
||||
</p>
|
||||
<div className="ml-[9px] border-[0px] border-l-[1px] my-[10px] border-dashed border-BORDER">
|
||||
<div
|
||||
className="grid gap-[20px] px-[20px] py-[10px] justify-start content-start"
|
||||
style={{
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 0fr))',
|
||||
gridAutoRows: '1fr'
|
||||
}}
|
||||
>
|
||||
{section.items.map((item, itemIndex) => (
|
||||
<Card
|
||||
key={itemIndex}
|
||||
title={item.title}
|
||||
className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer w-[300px] transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.05]"
|
||||
classNames={{
|
||||
header: 'border-b-[0px] p-[20px] pb-[10px] text-[14px] font-normal',
|
||||
body: 'p-[20px] pt-0 text-[12px] text-[#666]'
|
||||
}}
|
||||
onClick={() => window.open(item.link, '_blank')}
|
||||
>
|
||||
<span>{item.description}</span>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex gap-[8px] items-center">
|
||||
<Icon icon="ic:baseline-info" width="18" height="18" className="text-theme" />
|
||||
<div className="flex items-center w-full gap-4">
|
||||
<Button
|
||||
type="link"
|
||||
icon={<Icon icon="ic:baseline-open-in-new" width="18" height="18" />}
|
||||
iconPosition="end"
|
||||
classNames={{ icon: 'h-[22px] flex items-center' }}
|
||||
href="https://docs.apipark.com"
|
||||
target="_blank"
|
||||
className="text-[14px] font-bold px-0"
|
||||
>
|
||||
{$t('了解更多功能')}
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<Icon icon="ic:baseline-visibility-off" width="18" height="18" />}
|
||||
onClick={() => changeGuideShow((prev) => !prev)}
|
||||
classNames={{ icon: 'h-[22px] flex items-center' }}
|
||||
className="text-[14px] font-bold"
|
||||
>
|
||||
{$t('隐藏该教程')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function GuidePage() {
|
||||
const [showGuide, setShowGuide] = useState(localStorage.getItem('showGuide') !== 'false')
|
||||
const [showAdvancedGuide, setShowAdvancedGuide] = useState(localStorage.getItem('showAdvancedGuide') !== 'false')
|
||||
const [, forceUpdate] = useState<unknown>(null)
|
||||
const { state } = useGlobalContext()
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
setShowGuide(window.localStorage.getItem('showGuide') !== 'false')
|
||||
setShowAdvancedGuide(window.localStorage.getItem('showAdvancedGuide') !== 'false')
|
||||
}, [])
|
||||
|
||||
const guideSections = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: $t('快速接入 AI'),
|
||||
items: [
|
||||
{
|
||||
title: $t('配置你的 AI 模型'),
|
||||
description: $t('通过 APIPark 快速接入各种 AI 模型,使用统一的格式来调用API,并且可以随意切换模型。'),
|
||||
link: 'https://docs.apipark.com/docs/system_setting/ai_model_providers'
|
||||
},
|
||||
{
|
||||
title: $t('创建 AI 服务和 API'),
|
||||
description: $t('创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。'),
|
||||
link: 'https://docs.apipark.com/docs/services/ai_services'
|
||||
},
|
||||
{
|
||||
title: $t('创建调用 Token'),
|
||||
description: $t('为了安全地调用 API,你需要创建一个消费者以及Token。'),
|
||||
link: 'https://docs.apipark.com/docs/consumers'
|
||||
},
|
||||
{
|
||||
title: $t('调用'),
|
||||
description: $t('现在你可以通过 Token 来调用这些 API。'),
|
||||
link: 'https://docs.apipark.com/docs/call_api'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: $t('快速接入 REST API'),
|
||||
items: [
|
||||
{
|
||||
title: $t('创建 REST 服务和 API'),
|
||||
description: $t('创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。'),
|
||||
link: 'https://docs.apipark.com/docs/services/rest_services'
|
||||
},
|
||||
{
|
||||
title: $t('创建调用 Token'),
|
||||
description: $t('为了安全地调用 API,你需要创建一个消费者以及Token。'),
|
||||
link: 'https://docs.apipark.com/docs/consumers'
|
||||
},
|
||||
{
|
||||
title: $t('调用'),
|
||||
description: $t('现在你可以通过 Token 来调用这些 API。'),
|
||||
link: 'https://docs.apipark.com/docs/call_api'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: $t('仪表盘'),
|
||||
items: [
|
||||
{
|
||||
title: $t('统计 API 调用情况'),
|
||||
description: $t('仪表盘中提供了多种统计图表,帮助我们了解 API 的运行情况。'),
|
||||
link: 'https://docs.apipark.com/docs/analysis'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[state.language]
|
||||
)
|
||||
|
||||
const advanceGuideSections = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: $t('核心功能'),
|
||||
items: [
|
||||
{
|
||||
title: $t('账号与角色'),
|
||||
description: $t('邀请你的团队成员加入 APIPark,共同管理和调用 API。'),
|
||||
link: 'https://docs.apipark.com/docs/system_setting/account_role'
|
||||
},
|
||||
{
|
||||
title: $t('团队'),
|
||||
description: $t(
|
||||
'团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。'
|
||||
),
|
||||
link: 'https://docs.apipark.com/docs/teams'
|
||||
},
|
||||
{
|
||||
title: $t('服务'),
|
||||
description: $t('服务内包含一组 API,并且可以发布到 API 市场被其他团队使用。'),
|
||||
link: 'https://docs.apipark.com/docs/category/-%E6%9C%8D%E5%8A%A1'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: $t('权限管理'),
|
||||
items: [
|
||||
{
|
||||
title: $t('订阅服务'),
|
||||
description: $t(
|
||||
'如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审核后才可发起 API 请求。'
|
||||
),
|
||||
link: 'https://docs.apipark.com/docs/developer_portal'
|
||||
},
|
||||
{
|
||||
title: $t('审核订阅申请'),
|
||||
description: $t('提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的消费者才可发起 API 请求。'),
|
||||
link: 'https://docs.apipark.com/docs/services/review_consumers'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: $t('集成'),
|
||||
items: [
|
||||
{
|
||||
title: $t('日志'),
|
||||
description: $t('APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。'),
|
||||
link: 'https://docs.apipark.com/docs/system_setting/log/'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[state.language]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
window.localStorage.setItem('showGuide', showGuide.toString())
|
||||
}, [showGuide])
|
||||
|
||||
useEffect(() => {
|
||||
window.localStorage.setItem('showAdvancedGuide', showAdvancedGuide.toString())
|
||||
}, [showAdvancedGuide])
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname === '/guide') {
|
||||
router.replace('/guide/page')
|
||||
}
|
||||
}, [pathname, router])
|
||||
|
||||
useEffect(() => {
|
||||
forceUpdate({})
|
||||
}, [state.language])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1 h-full overflow-auto">
|
||||
<div className="border-[0px] mr-PAGE_INSIDE_X pt-[30px] pl-[40px]">
|
||||
<div className="mb-[30px]">
|
||||
<div className="flex justify-between mb-[20px] items-center">
|
||||
<div className="flex items-center gap-[8px] text-theme text-[26px]">
|
||||
<span>👋</span>
|
||||
<span>{$t('Hello!欢迎使用 APIPark')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-[8px]">
|
||||
<p>
|
||||
<span className="font-bold">🦄 APIPark </span>
|
||||
{$t(
|
||||
'是开源的一站式 AI 网关与 API 门户,可快速接入 OpenAI/DeepSeek 等各类 AI 模型,通过统一请求格式避免模型切换对业务造成影响,提供企业级 API 安全防护(鉴权/限流/敏感词过滤)与实时用量监控,支持团队内 API 共享协作,管理接口订阅授权并保证您的API安全。'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{$t('✨ 欢迎在 Github 为我们 Star 或提供产品反馈意见。')}
|
||||
<span className="font-bold">
|
||||
{$t('点击这里')}
|
||||
<span className="align-middle leading-[16px]">
|
||||
|
||||
<Icon icon="pajamas:arrow-right" width="16" height="16" />
|
||||
|
||||
</span>
|
||||
<a className="align-text-top" href="https://github.com/APIParkLab/APIPark" target="_blank">
|
||||
<img src="https://img.shields.io/github/stars/APIParkLab/APIPark?style=social" alt="" />
|
||||
</a>
|
||||
<span className="align-middle leading-[16px]">
|
||||
|
||||
<Icon icon="pajamas:arrow-right" width="16" height="16" />
|
||||
|
||||
</span>
|
||||
{$t('点击')}
|
||||
|
||||
<span className="align-middle leading-[16px]">
|
||||
<Icon icon="emojione:star" width="16" height="16" />
|
||||
</span>
|
||||
Star
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full pr-PAGE_INSIDE_X pb-PAGE_INSIDE_B pl-[40px]">
|
||||
<AIModelGuide />
|
||||
<div className="flex flex-col gap-[15px] pb-PAGE_INSIDE_B">
|
||||
{showGuide && (
|
||||
<Collapse
|
||||
size="large"
|
||||
expandIconPosition="end"
|
||||
defaultActiveKey={['1']}
|
||||
className="bg-[linear-gradient(153.41deg,rgba(244,245,255,1)_0.23%,rgba(255,255,255,1)_83.32%)] rounded-[10px] [&>.ant-collapse-item>.ant-collapse-content]:bg-transparent"
|
||||
items={[
|
||||
{
|
||||
key: '1',
|
||||
label: (
|
||||
<div className="">
|
||||
<p className="text-[14px] mb-[10px] flex gap-[8px] items-center font-bold">
|
||||
<span>🚀</span>
|
||||
<span>{`${$t('快速入门')}`}</span>{' '}
|
||||
</p>
|
||||
<p className="text-[12px]">{$t('我们提供了一些任务来帮你快速了解 APIPark')}</p>
|
||||
</div>
|
||||
),
|
||||
children: <QuickGuideContent changeGuideShow={setShowGuide} guideSections={guideSections} />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
{showAdvancedGuide && (
|
||||
<Collapse
|
||||
size="large"
|
||||
expandIconPosition="end"
|
||||
defaultActiveKey={['1']}
|
||||
className="bg-[linear-gradient(153.41deg,rgba(244,245,255,1)_0.23%,rgba(255,255,255,1)_83.32%)] rounded-[10px] [&>.ant-collapse-item>.ant-collapse-content]:bg-transparent"
|
||||
items={[
|
||||
{
|
||||
key: '1',
|
||||
label: (
|
||||
<div className="">
|
||||
<p className="text-[14px] mb-[10px] flex gap-[8px] items-center font-bold">
|
||||
<span>🏍️</span>
|
||||
<span>{`${$t('进阶教程')}`}</span>{' '}
|
||||
</p>
|
||||
<p className="text-[12px]">{$t('了解 APIPark 如何更好地管理 API 和 AI')}</p>
|
||||
</div>
|
||||
),
|
||||
children: <QuickGuideContent changeGuideShow={setShowAdvancedGuide} guideSections={advanceGuideSections} />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1,319 @@
|
||||
'use client'
|
||||
|
||||
import { StyleProvider } from '@ant-design/cssinjs'
|
||||
import { ProConfigProvider, ProLayout } from '@ant-design/pro-components'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import AvatarPic from '@common/assets/default-avatar.png'
|
||||
import Logo from '@common/assets/layout-logo.png'
|
||||
import LanguageSetting from '@common/components/aoplatform/LanguageSetting'
|
||||
import { BasicResponse, RESPONSE_TIPS, routerKeyMap, STATUS_CODE } from '@common/const/const'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { UserInfoType } from '@common/const/type'
|
||||
import { GlobalProvider, useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { LocaleProvider, useLocaleContext } from '@common/contexts/LocaleContext'
|
||||
import { PluginEventHubProvider } from '@common/contexts/PluginEventHubContext'
|
||||
import { PluginSlotHubProvider, usePluginSlotHub } from '@common/contexts/PluginSlotHubContext'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { $t } from '@common/locales'
|
||||
import { transformMenuData } from '@common/utils/navigation'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { App as AppAntd, Button, ConfigProvider, Dropdown, MenuProps, Spin } from 'antd'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { ReactNode, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
const themeToken = {
|
||||
bgLayout: '#17163E;',
|
||||
header: {
|
||||
heightLayoutHeader: 72
|
||||
},
|
||||
pageContainer: {
|
||||
paddingBlockPageContainerContent: 0,
|
||||
paddingInlinePageContainerContent: 0
|
||||
}
|
||||
}
|
||||
|
||||
function AdminShell({ children, project = 'core' }: { children: ReactNode; project?: string }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const { state, accessData, checkPermission, accessInit, dispatch, resetAccess, getGlobalAccessData, menuList } =
|
||||
useGlobalContext()
|
||||
const [currentPath, setCurrentPath] = useState(pathname)
|
||||
const mainPage = state.mainPage || (project === 'core' ? '/guide/page' : '/portal/list')
|
||||
const [menuItems, setMenuItems] = useState<MenuProps['items']>()
|
||||
const pluginSlotHub = usePluginSlotHub()
|
||||
const { message } = AppAntd.useApp()
|
||||
const [userInfo, setUserInfo] = useState<UserInfoType>()
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
useEffect(() => {
|
||||
setMenuItems(transformMenuData(menuList))
|
||||
}, [menuList, state.language, accessInit])
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname === '/') {
|
||||
router.push(mainPage)
|
||||
}
|
||||
}, [pathname, mainPage, router])
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPath(pathname)
|
||||
}, [pathname])
|
||||
|
||||
const headerMenuData = useMemo(() => {
|
||||
const hasAccess = (access: unknown) => checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
|
||||
const filterMenu = (menu: Array<{ [k: string]: unknown }>) => {
|
||||
return [...menu]
|
||||
.filter((x) => x)
|
||||
.map((item: any) => {
|
||||
if (item.routes && item.routes.length > 0) {
|
||||
const filteredRoutes: Array<{ [k: string]: unknown }> = filterMenu(item.routes)
|
||||
if (filteredRoutes.length === 0) {
|
||||
return false
|
||||
}
|
||||
return { ...item, routes: filteredRoutes, name: $t(item.name) }
|
||||
}
|
||||
if (item.access) {
|
||||
return item.access === 'all' || hasAccess(item.access) ? { ...item, name: $t(item.name) } : null
|
||||
}
|
||||
return { ...item, name: $t(item.name) }
|
||||
})
|
||||
.filter((x) => x)
|
||||
}
|
||||
|
||||
const res = [...(menuItems || [])]
|
||||
.filter((x) => x)
|
||||
.map((x: any) =>
|
||||
x.routes ? { ...x, name: $t(x.name), routes: filterMenu(x.routes) } : { ...x, name: $t(x.name) }
|
||||
)
|
||||
|
||||
return {
|
||||
path: '/',
|
||||
routes: res
|
||||
.map((x) => ({ ...x, routes: x.routes?.filter((routeItem: any) => routeItem.access || routeItem.routes?.length > 0) }))
|
||||
.filter((x) => x.access || x.routes?.length > 0)
|
||||
}
|
||||
}, [accessData, state.language, menuItems, checkPermission])
|
||||
|
||||
useEffect(() => {
|
||||
fetchData<BasicResponse<{ profile: UserInfoType }>>('account/profile', { method: 'GET' }).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setUserInfo(data.profile)
|
||||
dispatch({ type: 'UPDATE_USERDATA', userData: data.profile })
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
getGlobalAccessData()
|
||||
}, [])
|
||||
|
||||
const logOut = () => {
|
||||
fetchData<BasicResponse<null>>('account/logout', { method: 'GET' }).then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
dispatch({ type: 'LOGOUT' })
|
||||
resetAccess()
|
||||
router.push('/admin/login')
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const items: MenuProps['items'] = useMemo(
|
||||
() =>
|
||||
[
|
||||
!['guest', 'third-user'].includes(userInfo?.type as string) && {
|
||||
key: '2',
|
||||
label: (
|
||||
<Button
|
||||
key="changePsw"
|
||||
type="text"
|
||||
className="flex items-center p-0 bg-transparent border-none"
|
||||
onClick={() => router.push('/userProfile/changepsw')}
|
||||
>
|
||||
{$t('账号设置')}
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: (
|
||||
<Button key="logout" type="text" className="flex items-center p-0 bg-transparent border-none" onClick={logOut}>
|
||||
{$t('退出登录')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
].filter(Boolean),
|
||||
[userInfo, router]
|
||||
)
|
||||
|
||||
const actionRender = useMemo(() => {
|
||||
return [
|
||||
<LanguageSetting key="lang" />,
|
||||
<Button
|
||||
key="docs"
|
||||
className="text-[#ffffffb3] hover:text-[#fff] border-none"
|
||||
type="default"
|
||||
ghost
|
||||
onClick={() => window.open('https://docs.apipark.com', '_blank')}
|
||||
>
|
||||
<span className="flex items-center gap-[8px]">
|
||||
<Icon icon="ic:baseline-help" width="14" height="14" />
|
||||
{$t('文档')}
|
||||
</span>
|
||||
</Button>,
|
||||
...(((pluginSlotHub.getSlot('basicLayoutAfterBtns') as ReactNode[]) || []) as ReactNode[])
|
||||
]
|
||||
}, [state.language, pluginSlotHub])
|
||||
|
||||
const logoSrc = typeof Logo === 'string' ? Logo : (Logo as any)?.src
|
||||
const avatarSrc = userInfo?.avatar || (typeof AvatarPic === 'string' ? AvatarPic : (AvatarPic as any)?.src)
|
||||
|
||||
return (
|
||||
<div
|
||||
id="test-pro-layout"
|
||||
style={{
|
||||
height: '100vh',
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<ProConfigProvider hashed={false}>
|
||||
<ConfigProvider
|
||||
getTargetContainer={() => {
|
||||
return document.getElementById('test-pro-layout') || document.body
|
||||
}}
|
||||
>
|
||||
<ProLayout
|
||||
prefixCls="apipark-layout"
|
||||
location={{ pathname: currentPath }}
|
||||
siderWidth={220}
|
||||
breakpoint={'lg'}
|
||||
route={headerMenuData as any}
|
||||
token={themeToken}
|
||||
siderMenuType="group"
|
||||
menu={{ type: 'group', collapsedShowGroupTitle: true }}
|
||||
disableMobile={true}
|
||||
avatarProps={{
|
||||
src: avatarSrc,
|
||||
size: 'small',
|
||||
title: userInfo?.username || 'unknown',
|
||||
render: (props, dom) => (
|
||||
<Dropdown menu={{ items }}>
|
||||
<div className="avatar-dom">{dom}</div>
|
||||
</Dropdown>
|
||||
)
|
||||
}}
|
||||
actionsRender={(props) => {
|
||||
if (props.isMobile) return []
|
||||
return actionRender
|
||||
}}
|
||||
headerTitleRender={() => (
|
||||
<div className="w-[192px] flex items-center">
|
||||
<img className="h-[20px] cursor-pointer" src={logoSrc} onClick={() => router.push(mainPage)} alt="logo" />
|
||||
<a
|
||||
className="align-text-top ml-[5px] h-[25px] relative"
|
||||
href="https://github.com/APIParkLab/APIPark"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src="https://img.shields.io/github/stars/APIParkLab/APIPark?style=social"
|
||||
className="absolute top-[6px]"
|
||||
width={75}
|
||||
alt=""
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
logo={logoSrc}
|
||||
pageTitleRender={() => $t('APIPark')}
|
||||
menuFooterRender={(props) => {
|
||||
if (props?.collapsed) return undefined
|
||||
}}
|
||||
menuItemRender={(item, dom) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
if (
|
||||
item.key &&
|
||||
routerKeyMap.get(item.key as string) &&
|
||||
(routerKeyMap.get(item.key as string) as string[])?.length > 0 &&
|
||||
(routerKeyMap.get(item.key as string) as string[])?.indexOf(currentPath.split('/')[1]) !== -1
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (item.key === currentPath.split('/')[1]) {
|
||||
return
|
||||
}
|
||||
if (item.path) {
|
||||
router.push(item.path)
|
||||
}
|
||||
setCurrentPath(item.path || '')
|
||||
}}
|
||||
>
|
||||
{dom}
|
||||
</div>
|
||||
)}
|
||||
fixSiderbar={true}
|
||||
layout="mix"
|
||||
splitMenus={true}
|
||||
collapsed={false}
|
||||
collapsedButtonRender={false}
|
||||
>
|
||||
<div
|
||||
className={`w-full h-calc-100vh-minus-navbar ${currentPath.startsWith('/role/list') ? 'overflow-auto' : 'overflow-hidden'
|
||||
} ${currentPath.startsWith('/guide/page') ? '' : 'pl-PAGE_INSIDE_X pt-PAGE_INSIDE_T'}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</ProLayout>
|
||||
</ConfigProvider>
|
||||
</ProConfigProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AdminProviders({ children }: { children: ReactNode }) {
|
||||
const { locale } = useLocaleContext()
|
||||
|
||||
return (
|
||||
<StyleProvider hashPriority="high">
|
||||
<ConfigProvider locale={locale} wave={{ disabled: true }}>
|
||||
<PluginEventHubProvider>
|
||||
<GlobalProvider>
|
||||
<AppAntd className="h-full" message={{ maxCount: 1 }}>
|
||||
<PluginSlotHubProvider>
|
||||
<AdminShell project="core">{children}</AdminShell>
|
||||
</PluginSlotHubProvider>
|
||||
</AppAntd>
|
||||
</GlobalProvider>
|
||||
</PluginEventHubProvider>
|
||||
</ConfigProvider>
|
||||
</StyleProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default function AdminLayout({ children }: { children: ReactNode }) {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<Spin
|
||||
indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
|
||||
spinning={true}
|
||||
className="w-full h-full flex items-center justify-center"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<LocaleProvider>
|
||||
<AdminProviders>{children}</AdminProviders>
|
||||
</LocaleProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@/app/_legacy/LegacyAppPage";
|
||||
@@ -0,0 +1,15 @@
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export default async function ServiceLegacyFallbackPage({
|
||||
params
|
||||
}: {
|
||||
params: Promise<{ slug: string[] }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
|
||||
if (slug.length >= 4 && (slug[1] === 'inside' || slug[1] === 'aiInside')) {
|
||||
redirect(`/service/${slug[0]}/${slug[1]}/${slug[2]}/overview`)
|
||||
}
|
||||
|
||||
redirect('/service/list')
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ServiceDetailLegacyTabs } from '../../../../_components/ServiceDetailLegacyTabs'
|
||||
|
||||
export default function AiServiceApprovalRoutePage() {
|
||||
return <ServiceDetailLegacyTabs side="aiInside" type="approval" />
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user