mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
Compare commits
150 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1485b31226 | |||
| 4b8fa43c36 | |||
| e928cd84d7 | |||
| e91a9e7726 | |||
| dff6e722c0 | |||
| 025bd4c6cc | |||
| b2a8c8d901 | |||
| 0e1efc9656 | |||
| 5898337481 | |||
| aff2d1ce01 | |||
| a574ed8dae | |||
| 6b90770a92 | |||
| 217e31787c | |||
| 6ced12af75 | |||
| 9e7feff093 | |||
| 707d98ba34 | |||
| 78dc382e82 | |||
| a03e310418 | |||
| e485ae2511 | |||
| 526dddb579 | |||
| 16e5a37087 | |||
| 12c5ac85d3 | |||
| 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 | |||
| 5efd19ef7c | |||
| 28cd4fd91c | |||
| 82f4089f42 | |||
| cd33448446 | |||
| e5b50a7073 | |||
| 10bd352bf4 | |||
| e5c6e4fa82 | |||
| 1572e03dd1 | |||
| 61025763ed | |||
| 00905e4167 | |||
| a5a895e42d | |||
| ed1d19532b | |||
| bf990517dc | |||
| 83873c8c92 | |||
| a61e6ba67f | |||
| e081580786 | |||
| 813905ca40 | |||
| 66be761d18 | |||
| 6b6fa5bd40 | |||
| 943ef4f9b0 | |||
| 78d9a1c23c | |||
| 342d022c43 | |||
| a8c14ee839 | |||
| 23c40efe0d | |||
| a76941ea17 | |||
| 0c392d2092 | |||
| 46caf49f18 | |||
| 74c87ec308 | |||
| 8b318caa0b | |||
| 6bad1c3c7c | |||
| 1333d4ed02 | |||
| d3e91b04a2 | |||
| f4400c0130 | |||
| cc5d677d67 | |||
| e4c3cbc99b | |||
| 5c1db00d7e | |||
| cd91f4bdb9 | |||
| 79860bc665 | |||
| 4623ba6fba | |||
| 1f4acdc99e | |||
| 0307282dbd | |||
| 7dbc2a1a78 | |||
| 12d42c4247 | |||
| 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
|
||||
+62748
-60558
File diff suppressed because it is too large
Load Diff
@@ -190,6 +190,7 @@ func (i *imlMcpController) OnComplete() {
|
||||
i.server["ja-JP"] = server.NewSSEServer(i.generateJPMCPServer(), server.WithBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
||||
|
||||
i.openServer = server.NewSSEServer(enSer, server.WithBasePath(fmt.Sprintf("/openapi/v1/%s", strings.Trim(mcp_server.GlobalBasePath, "/"))))
|
||||
|
||||
}
|
||||
|
||||
func (i *imlMcpController) GlobalMCPHandle(ctx *gin.Context) {
|
||||
@@ -263,6 +264,28 @@ func (i *imlMcpController) ServiceHandleMessage(ctx *gin.Context) {
|
||||
i.handleMessage(ctx, mcp_server.DefaultMCPServer())
|
||||
}
|
||||
|
||||
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.CheckAPIKeyAuthorization(ctx, serviceId, apikey)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": err.Error(), "success": "fail"})
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid apikey", "success": "fail"})
|
||||
return
|
||||
}
|
||||
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))
|
||||
mcp_server.DefaultMCPServer().ServeHTTP(ctx.Writer, req)
|
||||
}
|
||||
|
||||
func (i *imlMcpController) handleMessage(ctx *gin.Context, server http.Handler) {
|
||||
sessionId := ctx.Request.URL.Query().Get("sessionId")
|
||||
apikey, ok := i.sessionKeys.Load(sessionId)
|
||||
|
||||
@@ -15,6 +15,7 @@ type IMcpController interface {
|
||||
ServiceHandleSSE(ctx *gin.Context)
|
||||
ServiceHandleMessage(ctx *gin.Context)
|
||||
GlobalMCPConfig(ctx *gin.Context) (string, error)
|
||||
ServiceHandleStreamHTTP(ctx *gin.Context)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=core --stream --verbose ",
|
||||
"build": "set NODE_OPTIONS=--max-old-space-size=8192 && lerna run build --scope=core --stream --verbose ",
|
||||
"serve": "lerna run preview --parallel",
|
||||
"serve:remotes": "lerna run serve --scope=remote --parallel",
|
||||
"dev": "lerna run dev --scope=core --stream",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
@@ -137,7 +137,7 @@ function BasicLayout({ project = 'core' }: { project: string }) {
|
||||
const items: MenuProps['items'] = useMemo(
|
||||
() =>
|
||||
[
|
||||
userInfo?.type !== 'guest' && {
|
||||
!['guest', 'third-user'].includes(userInfo?.type as string) && {
|
||||
key: '2',
|
||||
label: (
|
||||
<Button
|
||||
|
||||
@@ -119,6 +119,11 @@ export const PERMISSION_DEFINITION = [
|
||||
anyOf: [{ backend: ['system.organization.role.manager_team_role'] }]
|
||||
}
|
||||
},
|
||||
'system.organization.auth.view': {
|
||||
granted: {
|
||||
anyOf: [{ backend: ['system.settings.login.manager', 'system.settings.login.view'] }]
|
||||
}
|
||||
},
|
||||
'system.api_market.service_classification.view': {
|
||||
granted: {
|
||||
anyOf: [{ backend: ['system.settings.general.view'] }]
|
||||
@@ -634,6 +639,15 @@ export const PERMISSION_DEFINITION = [
|
||||
]
|
||||
}
|
||||
},
|
||||
'team.consumer.mcp.view': {
|
||||
granted: {
|
||||
anyOf: [
|
||||
{
|
||||
backend: ['team.consumer.mcp.manager', 'team.consumer.mcp.view']
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
'team.application.authorization.add': {
|
||||
granted: {
|
||||
anyOf: [{ backend: ['system.workspace.application.manager_all', 'team.consumer.authorization.manager'] }]
|
||||
|
||||
@@ -207,8 +207,15 @@ const mockData = [
|
||||
name: '角色',
|
||||
key: 'role',
|
||||
path: '/role',
|
||||
icon: 'ic:baseline-verified-user',
|
||||
icon: 'ph:user-circle-gear-fill',
|
||||
access: 'system.organization.role.view'
|
||||
},
|
||||
{
|
||||
name: '鉴权',
|
||||
key: 'auth',
|
||||
path: '/auth',
|
||||
icon: 'ic:baseline-verified-user',
|
||||
access: 'system.organization.auth.view'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -60,6 +60,16 @@ const mockData = {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
driver: 'apipark.builtIn.component',
|
||||
name: 'auth',
|
||||
router: [
|
||||
{
|
||||
path: 'auth',
|
||||
type: 'normal'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
driver: 'apipark.builtIn.component',
|
||||
name: 'cluster',
|
||||
|
||||
@@ -461,6 +461,17 @@
|
||||
"待审核": "K35612f29",
|
||||
"已审核": "K47eaafde",
|
||||
"发布申请": "K56b4254f",
|
||||
"鉴权": "Kb35e6a18",
|
||||
"系统用户账号登录授权配置": "K679bd7e4",
|
||||
"授权类型": "K9e7bb257",
|
||||
"请选择授权类型": "Kc499fc1d",
|
||||
"APP ID": "Kee9f8f26",
|
||||
"请输入APP ID": "K9e4c19bb",
|
||||
"APP ID 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上": "K45c99f97",
|
||||
"APP Secret": "K90f7c3b4",
|
||||
"请输入APP Secret": "Kdc53d96f",
|
||||
"APP Secret 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上": "K56e77c4",
|
||||
"启用授权": "K50693bd8",
|
||||
"API 调用地址": "Kea2f9279",
|
||||
"API base URL 一般设置为API 网关的外部网络访问地址,或者是API网关绑定的域名。": "K7fc496a1",
|
||||
"OpenAPI & MCP 调用地址": "Ka7ca8fde",
|
||||
@@ -553,6 +564,7 @@
|
||||
"请输入密码": "K25c895d5",
|
||||
"密码": "K551b0348",
|
||||
"登录": "Kd2c1a316",
|
||||
"飞书授权登录": "K682b11cb",
|
||||
"访客模式": "K192b3e38",
|
||||
"您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。": "K91aa4801",
|
||||
"Version (0)-(1)": "K480045ce",
|
||||
@@ -564,6 +576,8 @@
|
||||
"AI 代理集成": "Ke6908f16",
|
||||
"请先订阅该服务": "K71ed51fa",
|
||||
"申请": "K4aa9ed2c",
|
||||
"未配置 API Key": "Kf7b54a1",
|
||||
"配置": "K2a1422d2",
|
||||
"选择 API Key": "K1bec8cbe",
|
||||
"新增 API Key": "Kb0e0aeda",
|
||||
"API 密钥可用于调用系统级 Open API 和 MCP。": "K9d81999c",
|
||||
|
||||
@@ -972,5 +972,19 @@
|
||||
"K6c267c7b": "Avg Requests per Subscriber",
|
||||
"K133d4291": "Avg Traffic per Subscriber",
|
||||
"K37c5f1d0": "Token",
|
||||
"Kb98264d4": "Avg Token per Subscriber"
|
||||
"Kb98264d4": "Avg Token per Subscriber",
|
||||
"Kb35e6a18": "Auth",
|
||||
"K679bd7e4": "System User Account Login Authorization Settings",
|
||||
"K9e7bb257": "Authorization Type",
|
||||
"Kc499fc1d": "Please select an authorization type",
|
||||
"Kee9f8f26": "APP ID",
|
||||
"K9e4c19bb": "Please enter APP ID",
|
||||
"K90f7c3b4": "APP Secret",
|
||||
"Kdc53d96f": "Please enter APP Secret",
|
||||
"K50693bd8": "Enable Authorization",
|
||||
"K682b11cb": "Feishu Authorization Login",
|
||||
"K45c99f97": "The APP ID parameter can be found on the App Credentials and Basic Information page in the Feishu Developer Console",
|
||||
"K56e77c4": "The APP Secret parameter can be found on the App Credentials and Basic Information page in the Feishu Developer Console",
|
||||
"Kf7b54a1": "API Key not configured",
|
||||
"K2a1422d2": "Configure"
|
||||
}
|
||||
|
||||
@@ -994,5 +994,19 @@
|
||||
"K6c267c7b": "消費者あたりの平均リクエスト数",
|
||||
"K133d4291": "消費者あたりの平均ネットワークトラフィック",
|
||||
"K37c5f1d0": "トークン消費量",
|
||||
"Kb98264d4": "消費者あたりの平均トークン消費量"
|
||||
"Kb98264d4": "消費者あたりの平均トークン消費量",
|
||||
"Kb35e6a18": "認証",
|
||||
"K679bd7e4": "システムユーザーアカウントログイン認可設定",
|
||||
"K9e7bb257": "認可タイプ",
|
||||
"Kc499fc1d": "認可タイプを選択してください",
|
||||
"Kee9f8f26": "APP ID",
|
||||
"K9e4c19bb": "APP ID を入力してください",
|
||||
"K90f7c3b4": "APP Secret",
|
||||
"Kdc53d96f": "APP Secret を入力してください",
|
||||
"K50693bd8": "認可を有効化",
|
||||
"K682b11cb": "Feishu 認証ログイン",
|
||||
"K45c99f97": "APP ID パラメータは Feishu 開発者コンソールのアプリ認証情報と基本情報ページにあります",
|
||||
"K56e77c4": "APP Secret パラメータは Feishu 開発者コンソールのアプリ認証情報と基本情報ページにあります",
|
||||
"Kf7b54a1": "API Key が設定されていません",
|
||||
"K2a1422d2": "設定"
|
||||
}
|
||||
|
||||
@@ -923,5 +923,19 @@
|
||||
"K6c267c7b": "平均每消费者的请求次数",
|
||||
"K133d4291": "平均每消费者的网络流量",
|
||||
"K37c5f1d0": "Token 消耗",
|
||||
"Kb98264d4": "平均每消费者的 Token 消耗"
|
||||
"Kb98264d4": "平均每消费者的 Token 消耗",
|
||||
"Kb35e6a18": "鉴权",
|
||||
"K679bd7e4": "系统用户账号登录授权配置",
|
||||
"K9e7bb257": "授权类型",
|
||||
"Kc499fc1d": "请选择授权类型",
|
||||
"Kee9f8f26": "APP ID",
|
||||
"K9e4c19bb": "请输入APP ID",
|
||||
"K90f7c3b4": "APP Secret",
|
||||
"Kdc53d96f": "请输入APP Secret",
|
||||
"K50693bd8": "启用授权",
|
||||
"K682b11cb": "飞书授权登录",
|
||||
"K45c99f97": "APP ID 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上",
|
||||
"K56e77c4": "APP Secret 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上",
|
||||
"Kf7b54a1": "未配置 API Key",
|
||||
"K2a1422d2": "配置"
|
||||
}
|
||||
|
||||
@@ -994,5 +994,19 @@
|
||||
"K6c267c7b": "平均每位使用者的請求次數",
|
||||
"K133d4291": "平均每位使用者的網路流量",
|
||||
"K37c5f1d0": "Token 消耗",
|
||||
"Kb98264d4": "平均每位使用者的 Token 消耗"
|
||||
"Kb98264d4": "平均每位使用者的 Token 消耗",
|
||||
"Kb35e6a18": "鑑權",
|
||||
"K679bd7e4": "系統用戶帳號登入授權配置",
|
||||
"K9e7bb257": "授權類型",
|
||||
"Kc499fc1d": "請選擇授權類型",
|
||||
"Kee9f8f26": "APP ID",
|
||||
"K9e4c19bb": "請輸入 APP ID",
|
||||
"K90f7c3b4": "APP Secret",
|
||||
"Kdc53d96f": "請輸入 APP Secret",
|
||||
"K50693bd8": "啟用授權",
|
||||
"K682b11cb": "飛書授權登入",
|
||||
"K45c99f97": "APP ID 參數位於飛書開發人員控制台中的應用程式憑證與基礎資訊頁面",
|
||||
"K56e77c4": "APP Secret 參數位於飛書開發人員控制台中的應用程式憑證與基礎資訊頁面",
|
||||
"Kf7b54a1": "未配置 API Key",
|
||||
"K2a1422d2": "配置"
|
||||
}
|
||||
|
||||
@@ -618,6 +618,16 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
|
||||
/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementAppSetting.tsx'
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
path: 'mcp',
|
||||
key: 'consumerMcp',
|
||||
lazy: lazy(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/mcpContent.tsx'
|
||||
)
|
||||
)
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -693,6 +703,14 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
'auth',
|
||||
{
|
||||
type: 'module',
|
||||
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/auth/Auth.tsx')),
|
||||
key: 'auth'
|
||||
}
|
||||
],
|
||||
[
|
||||
'analytics',
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ export type MemberTableListItem = {
|
||||
enable:boolean
|
||||
departmentId:string
|
||||
roles:EntityItem[]
|
||||
from: string
|
||||
};
|
||||
|
||||
export type AddToDepartmentProps = {
|
||||
@@ -40,7 +41,7 @@ export type MemberDropdownModalFieldType = {
|
||||
|
||||
export type MemberDropdownModalProps = {
|
||||
type:'addDep'|'addChild'|'addMember'|'editMember'|'rename'
|
||||
entity?:(MemberTableListItem & {departmentIds:string[]}) | ({id?:string, departmentIds?:string[],name?:string})
|
||||
entity?:(MemberTableListItem & {departmentIds:string[]}) | ({id?:string, departmentIds?:string[],name?:string,from?:string})
|
||||
selectedMemberGroupId?:string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,221 +1,405 @@
|
||||
import {FC, useCallback, useEffect, useRef, useState} from "react";
|
||||
import {App, Button, Divider, Form, FormInstance, Input, Spin, Tooltip} from "antd";
|
||||
import {useGlobalContext} from "@common/contexts/GlobalStateContext.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {BasicResponse, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { App, Button, Divider, Form, FormInstance, Input, Spin, Tooltip } from 'antd'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { BasicResponse, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
// import {useCrypto} from "../hooks/crypto.ts";
|
||||
import Logo from '@common/assets/layout-logo.png'
|
||||
import { $t } from "@common/locales";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import LanguageSetting from "@common/components/aoplatform/LanguageSetting";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import FeishuLogo from '@common/assets/feishu.png'
|
||||
import { $t } from '@common/locales'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import LanguageSetting from '@common/components/aoplatform/LanguageSetting'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
|
||||
const Login:FC = ()=> {
|
||||
const {state, dispatch} = useGlobalContext()
|
||||
const {fetchData} = useFetch()
|
||||
const { message } = App.useApp()
|
||||
const navigate = useNavigate();
|
||||
const formRef = useRef<FormInstance>(null);
|
||||
const [loading,setLoading] = useState<boolean>()
|
||||
const [allowGuest, setAllowGuest] = useState<boolean>(false)
|
||||
const [spinning,setSpinning] = useState<boolean>(false)
|
||||
const Login: FC = () => {
|
||||
const { state, dispatch } = useGlobalContext()
|
||||
const { fetchData } = useFetch()
|
||||
const { message } = App.useApp()
|
||||
const navigate = useNavigate()
|
||||
const formRef = useRef<FormInstance>(null)
|
||||
const [loading, setLoading] = useState<boolean>()
|
||||
const [allowGuest, setAllowGuest] = useState<boolean>(false)
|
||||
const [spinning, setSpinning] = useState<boolean>(false)
|
||||
// 是否允许飞书登录
|
||||
const [allowFeishuLogin, setAllowFeishuLogin] = useState<boolean>(false)
|
||||
// 飞书登录app_id
|
||||
const [feishuAppId, setFeishuAppId] = useState<string>()
|
||||
// 获取 url 参数
|
||||
const query = new URLSearchParams(useLocation().search)
|
||||
// 是否是飞书登录
|
||||
const [isFeishuLogin, setIsFeishuLogin] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (isFeishuLogin) {
|
||||
const callbackUrl = new URLSearchParams(window.location.search).get('callbackUrl')
|
||||
if (callbackUrl && callbackUrl !== 'null') {
|
||||
navigate(callbackUrl)
|
||||
} else {
|
||||
navigate(state.mainPage)
|
||||
}
|
||||
setIsFeishuLogin(false)
|
||||
}
|
||||
}, [isFeishuLogin])
|
||||
/**
|
||||
* 飞书登录
|
||||
* @param feishuCode 飞书 code
|
||||
*/
|
||||
const feishuLogin = async (feishuCode: string) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const feishuCallbackUrl = localStorage.getItem('feishuCallbackUrl')
|
||||
const { code, msg } = await fetchData<BasicResponse<null>>('account/login/feishu', {
|
||||
method: 'POST',
|
||||
eoBody: {
|
||||
code: feishuCode,
|
||||
redirect_uri: feishuCallbackUrl
|
||||
}
|
||||
})
|
||||
|
||||
const check = useCallback(()=>{
|
||||
state.isAuthenticated &&setSpinning(true)
|
||||
fetchData<BasicResponse<{channel:Array<{name:string}>, status:string}>>('account/login',{method:'GET'}).then(response=>{
|
||||
const {code,data} = response
|
||||
if(code === STATUS_CODE.SUCCESS && data.status !== 'anonymous'){
|
||||
dispatch({type:'LOGIN'})
|
||||
navigate(state.mainPage,{replace:true})
|
||||
}else{
|
||||
dispatch({type:'LOGOUT'})
|
||||
setAllowGuest(data.channel.filter(x=>x.name === 'guest_access').length > 0)
|
||||
setSpinning(false)
|
||||
}
|
||||
})
|
||||
},[])
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
dispatch({ type: 'LOGIN' })
|
||||
setIsFeishuLogin(true)
|
||||
} else {
|
||||
dispatch({ type: 'LOGOUT' })
|
||||
setIsFeishuLogin(false)
|
||||
message.error(msg)
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const check = useCallback(() => {
|
||||
state.isAuthenticated && setSpinning(true)
|
||||
fetchData<BasicResponse<{ channel: Array<{ name: string; config: { [key: string]: any } }>; status: string }>>(
|
||||
'account/login',
|
||||
{ method: 'GET' }
|
||||
).then((response) => {
|
||||
const { code, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS && data.status !== 'anonymous') {
|
||||
dispatch({ type: 'LOGIN' })
|
||||
navigate(state.mainPage, { replace: true })
|
||||
} else {
|
||||
dispatch({ type: 'LOGOUT' })
|
||||
setAllowGuest(data.channel.filter((x: any) => x.name === 'guest_access').length > 0)
|
||||
const feishu = data.channel.find((x: any) => x.name === 'feishu')
|
||||
if (feishu) {
|
||||
setFeishuAppId(feishu.config.client_id)
|
||||
setAllowFeishuLogin(true)
|
||||
}
|
||||
const code = query.get('code')
|
||||
if (code) {
|
||||
feishuLogin(code)
|
||||
setSpinning(false)
|
||||
return
|
||||
}
|
||||
if (isInFeishuClient() && feishu) {
|
||||
openFeishuLogin(feishu.config.client_id)
|
||||
}
|
||||
setSpinning(false)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
const getSystemInfo = useCallback(() => {
|
||||
fetchData<BasicResponse<{ version: string; buildTime: string }>>('common/version', {
|
||||
method: 'GET',
|
||||
eoTransformKeys: ['build_time']
|
||||
}).then((response) => {
|
||||
const { code, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
dispatch({ type: 'UPDATE_VERSION', version: data.version })
|
||||
dispatch({ type: 'UPDATE_DATE', updateDate: data.buildTime })
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
const fetchLogin = async (values: any) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const { username, password } = values
|
||||
// const encryptedPassword = encryptByEnAES(username, password);
|
||||
|
||||
const body = {
|
||||
name: username,
|
||||
password: password
|
||||
// client: 1,
|
||||
// type: 1,
|
||||
// app_type: 4,
|
||||
}
|
||||
|
||||
const { code, msg } = await fetchData<BasicResponse<null>>('account/login/username', {
|
||||
method: 'POST',
|
||||
eoBody: body
|
||||
})
|
||||
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
dispatch({ type: 'LOGIN' })
|
||||
// message.success($t(RESPONSE_TIPS.loginSuccess));
|
||||
const callbackUrl = new URLSearchParams(window.location.search).get('callbackUrl')
|
||||
if (callbackUrl && callbackUrl !== 'null') {
|
||||
navigate(callbackUrl)
|
||||
} else {
|
||||
navigate(state.mainPage)
|
||||
}
|
||||
} else {
|
||||
dispatch({ type: 'LOGOUT' })
|
||||
message.error(msg)
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
const login = async () => {
|
||||
if (formRef.current) {
|
||||
const values = await formRef.current.validateFields()
|
||||
fetchLogin(values)
|
||||
}
|
||||
}
|
||||
|
||||
const loginAsGuest = () => {
|
||||
fetchLogin({ username: 'guest', password: '12345678' })
|
||||
}
|
||||
|
||||
const isInFeishuClient = () => {
|
||||
// 方法1:检查User-Agent
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
const isLark = ua.includes('lark') || ua.includes('feishu');
|
||||
|
||||
const getSystemInfo = useCallback(()=>{
|
||||
fetchData<BasicResponse<{version:string, buildTime:string}>>('common/version',{method:'GET', eoTransformKeys:['build_time']}).then(response=>{
|
||||
const {code,data} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
dispatch({type:'UPDATE_VERSION',version:data.version})
|
||||
dispatch({type:'UPDATE_DATE',updateDate:data.buildTime})
|
||||
}
|
||||
})
|
||||
},[])
|
||||
// 方法2:检查全局对象
|
||||
const hasSDK = typeof window.h5sdk !== 'undefined' || typeof window.tt !== 'undefined';
|
||||
|
||||
// 方法3:检查URL参数
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const hasFeishuParams = params.has('from') || params.has('required_launch_ability');
|
||||
|
||||
return isLark || hasSDK || hasFeishuParams;
|
||||
}
|
||||
|
||||
// 打开飞书授权页面
|
||||
const openFeishuLogin = (id?: string) => {
|
||||
const href = window.location.origin + window.location.pathname
|
||||
const authUrl = `https://accounts.feishu.cn/open-apis/authen/v1/authorize?client_id=${id || feishuAppId}&redirect_uri=${href}`
|
||||
localStorage.setItem('feishuCallbackUrl', href)
|
||||
window.location.href = authUrl
|
||||
}
|
||||
|
||||
const fetchLogin = async (values:any)=>{
|
||||
try {
|
||||
setLoading(true);
|
||||
const { username, password } = values;
|
||||
// const encryptedPassword = encryptByEnAES(username, password);
|
||||
useEffect(() => {
|
||||
check()
|
||||
getSystemInfo()
|
||||
}, [])
|
||||
|
||||
const body = {
|
||||
name:username,
|
||||
password: password
|
||||
// client: 1,
|
||||
// type: 1,
|
||||
// app_type: 4,
|
||||
};
|
||||
return spinning ? (
|
||||
<Spin
|
||||
indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
|
||||
spinning={spinning}
|
||||
className="w-full h-full flex items-center justify-center"
|
||||
></Spin>
|
||||
) : (
|
||||
<div className="h-full w-full flex flex-col items-center overflow-auto min-h-[490px] bg-[#0d1117]">
|
||||
<div id="glow-background" className="background-container">
|
||||
<svg className="background-pattern" aria-hidden="true">
|
||||
<defs>
|
||||
<pattern id="pattern-bg" width="200" height="200" patternUnits="userSpaceOnUse">
|
||||
<path d="M.5 200V.5H200" fill="none"></path>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#pattern-bg)"></rect>
|
||||
</svg>
|
||||
|
||||
const {code,msg } = await fetchData<BasicResponse<null>>('account/login/username',{method:'POST',eoBody:(body)})
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:svgjs="http://svgjs.dev/svgjs"
|
||||
viewBox="0 0 800 450"
|
||||
opacity="1"
|
||||
>
|
||||
<defs>
|
||||
<filter
|
||||
id="bbblurry-filter"
|
||||
x="-100%"
|
||||
y="-100%"
|
||||
width="400%"
|
||||
height="400%"
|
||||
filterUnits="objectBoundingBox"
|
||||
primitiveUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feGaussianBlur
|
||||
stdDeviation="99"
|
||||
x="0%"
|
||||
y="0%"
|
||||
width="100%"
|
||||
height="100%"
|
||||
in="SourceGraphic"
|
||||
edgeMode="none"
|
||||
result="blur"
|
||||
></feGaussianBlur>
|
||||
</filter>
|
||||
</defs>
|
||||
<g filter="url(#bbblurry-filter)">
|
||||
<ellipse
|
||||
rx="80.5"
|
||||
ry="66.5"
|
||||
cx="623.0285107902043"
|
||||
cy="25.708028895006635"
|
||||
fill="hsla(187, 67%, 50%, 1.00)"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="hsla(187, 67%, 50%, 1.00); hsla(340, 85%, 60%, 1.00); hsla(60, 90%, 55%, 1.00); hsla(187, 67%, 50%, 1.00)"
|
||||
dur="6s"
|
||||
repeatCount="indefinite"
|
||||
></animate>
|
||||
</ellipse>
|
||||
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
dispatch({type:'LOGIN'})
|
||||
// message.success($t(RESPONSE_TIPS.loginSuccess));
|
||||
const callbackUrl = new URLSearchParams(window.location.search).get('callbackUrl');
|
||||
if (callbackUrl && callbackUrl !== 'null') {
|
||||
navigate(callbackUrl);
|
||||
} else {
|
||||
navigate(state.mainPage);
|
||||
}
|
||||
}else{
|
||||
dispatch({type:'LOGOUT'})
|
||||
message.error(msg)
|
||||
}
|
||||
<ellipse
|
||||
rx="80.5"
|
||||
ry="66.5"
|
||||
cx="446.471435546875"
|
||||
cy="-11.694503784179688"
|
||||
fill="hsla(234, 78%, 61%, 1.00)"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="hsla(234, 78%, 61%, 1.00); hsla(100, 75%, 60%, 1.00); hsla(290, 80%, 70%, 1.00); hsla(234, 78%, 61%, 1.00)"
|
||||
dur="8s"
|
||||
repeatCount="indefinite"
|
||||
></animate>
|
||||
</ellipse>
|
||||
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
const login = async () => {
|
||||
if (formRef.current) {
|
||||
const values = await formRef.current.validateFields();
|
||||
fetchLogin(values);
|
||||
}
|
||||
};
|
||||
<ellipse
|
||||
rx="80.5"
|
||||
ry="66.5"
|
||||
cx="200.54574247724838"
|
||||
cy="-19.02454901710908"
|
||||
fill="hsla(167, 87%, 56%, 1.00)"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="hsla(167, 87%, 56%, 1.00); hsla(10, 90%, 65%, 1.00); hsla(300, 85%, 50%, 1.00); hsla(167, 87%, 56%, 1.00)"
|
||||
dur="10s"
|
||||
repeatCount="indefinite"
|
||||
></animate>
|
||||
</ellipse>
|
||||
|
||||
const loginAsGuest = ()=>{
|
||||
fetchLogin({username:'guest',password:'12345678'})
|
||||
}
|
||||
<ellipse rx="80.5" ry="66.5" cx="340.05827594708103" cy="-9.424536458161867" fill="hsl(25, 100%, 64%)">
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="hsl(25, 100%, 64%); hsl(200, 100%, 70%); hsl(50, 95%, 55%); hsl(25, 100%, 64%)"
|
||||
dur="8s"
|
||||
repeatCount="indefinite"
|
||||
></animate>
|
||||
</ellipse>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
{/* <div className="w-full border-box text-right pr-[40px]"></div> */}
|
||||
<div className="mx-auto flex-1 flex flex-col items-center justify-center z-[3]">
|
||||
<div className="mx-auto">
|
||||
<span className="flex items-center justify-center">
|
||||
<img className="h-[40px] mr-[8px]" src={Logo} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
useEffect(() => {
|
||||
check()
|
||||
getSystemInfo()
|
||||
}, []);
|
||||
<section className="block w-[410px] mx-auto mt-[46px] p-[30px] box-border rounded-[10px] shadow-[0_5px_20px_0_rgba(0,0,0,5%)] login-block">
|
||||
<div className="h-full">
|
||||
<div className="">
|
||||
<Form onFinish={login} className="w-[350px]" ref={formRef}>
|
||||
<Form.Item
|
||||
className="p-0 bg-transparent rounded border-none"
|
||||
name="username"
|
||||
rules={[{ required: true, message: $t('请输入账号'), whitespace: true }]}
|
||||
>
|
||||
<Input
|
||||
className="w-[350px] h-[40px] login-input"
|
||||
placeholder={$t('账号')}
|
||||
autoComplete="on"
|
||||
autoFocus
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
return (
|
||||
spinning?
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={spinning} className='w-full h-full flex items-center justify-center'></Spin> :
|
||||
<div className="h-full w-full flex flex-col items-center overflow-auto min-h-[490px] bg-[#0d1117]">
|
||||
<div id="glow-background" className="background-container">
|
||||
<svg className="background-pattern" aria-hidden="true">
|
||||
<defs>
|
||||
<pattern id="pattern-bg" width="200" height="200" patternUnits="userSpaceOnUse">
|
||||
<path d="M.5 200V.5H200" fill="none"></path>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#pattern-bg)"></rect>
|
||||
</svg>
|
||||
<Form.Item
|
||||
className="p-0 bg-transparent rounded border-none "
|
||||
name="password"
|
||||
rules={[{ required: true, message: $t('请输入密码') }]}
|
||||
>
|
||||
<Input.Password
|
||||
className="w-[350px] h-[40px] login-input"
|
||||
placeholder={$t('密码')}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" viewBox="0 0 800 450" opacity="1">
|
||||
<defs>
|
||||
<filter id="bbblurry-filter" x="-100%" y="-100%" width="400%" height="400%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur stdDeviation="99" x="0%" y="0%" width="100%" height="100%" in="SourceGraphic" edgeMode="none" result="blur"></feGaussianBlur>
|
||||
</filter>
|
||||
</defs>
|
||||
<g filter="url(#bbblurry-filter)">
|
||||
<ellipse rx="80.5" ry="66.5" cx="623.0285107902043" cy="25.708028895006635" fill="hsla(187, 67%, 50%, 1.00)">
|
||||
<animate attributeName="fill" values="hsla(187, 67%, 50%, 1.00); hsla(340, 85%, 60%, 1.00); hsla(60, 90%, 55%, 1.00); hsla(187, 67%, 50%, 1.00)" dur="6s" repeatCount="indefinite"></animate>
|
||||
</ellipse>
|
||||
|
||||
<ellipse rx="80.5" ry="66.5" cx="446.471435546875" cy="-11.694503784179688" fill="hsla(234, 78%, 61%, 1.00)">
|
||||
<animate attributeName="fill" values="hsla(234, 78%, 61%, 1.00); hsla(100, 75%, 60%, 1.00); hsla(290, 80%, 70%, 1.00); hsla(234, 78%, 61%, 1.00)" dur="8s" repeatCount="indefinite"></animate>
|
||||
</ellipse>
|
||||
|
||||
<ellipse rx="80.5" ry="66.5" cx="200.54574247724838" cy="-19.02454901710908" fill="hsla(167, 87%, 56%, 1.00)">
|
||||
<animate attributeName="fill" values="hsla(167, 87%, 56%, 1.00); hsla(10, 90%, 65%, 1.00); hsla(300, 85%, 50%, 1.00); hsla(167, 87%, 56%, 1.00)" dur="10s" repeatCount="indefinite"></animate>
|
||||
</ellipse>
|
||||
|
||||
<ellipse rx="80.5" ry="66.5" cx="340.05827594708103" cy="-9.424536458161867" fill="hsl(25, 100%, 64%)">
|
||||
<animate attributeName="fill" values="hsl(25, 100%, 64%); hsl(200, 100%, 70%); hsl(50, 95%, 55%); hsl(25, 100%, 64%)" dur="8s" repeatCount="indefinite"></animate>
|
||||
</ellipse>
|
||||
</g>
|
||||
</svg>
|
||||
<Form.Item className="p-0 bg-transparent rounded border-none ">
|
||||
<Button
|
||||
loading={loading}
|
||||
className="h-[40px] mt-mbase w-full inline-flex justify-center items-center"
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
>
|
||||
{$t('登录')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
{allowFeishuLogin && (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<Form.Item className="p-0 bg-transparent rounded border-none mb-0">
|
||||
<Button
|
||||
loading={loading}
|
||||
className="h-[40px] w-full inline-flex justify-center items-center"
|
||||
type="default"
|
||||
onClick={() => openFeishuLogin(feishuAppId)}
|
||||
>
|
||||
<img className="h-[30px]" src={FeishuLogo} />
|
||||
{$t('飞书授权登录')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{allowGuest && (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<Form.Item className="p-0 bg-transparent rounded border-none mb-0">
|
||||
<Button
|
||||
loading={loading}
|
||||
className="h-[40px] w-full inline-flex justify-center items-center"
|
||||
type="default"
|
||||
onClick={loginAsGuest}
|
||||
>
|
||||
{$t('访客模式')}{' '}
|
||||
<Tooltip
|
||||
title={$t(
|
||||
'您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。'
|
||||
)}
|
||||
>
|
||||
<Icon icon="ic:baseline-help" height={18} width={18} />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
{/* <div className="w-full border-box text-right pr-[40px]"></div> */}
|
||||
<div className="mx-auto flex-1 flex flex-col items-center justify-center z-[3]" >
|
||||
<div className="mx-auto">
|
||||
<span className="flex items-center justify-center">
|
||||
<img
|
||||
className="h-[40px] mr-[8px]"
|
||||
src={Logo}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="block w-[410px] mx-auto mt-[46px] p-[30px] box-border rounded-[10px] shadow-[0_5px_20px_0_rgba(0,0,0,5%)] login-block">
|
||||
<div className="h-full">
|
||||
<div className="">
|
||||
<Form onFinish={login} className="w-[350px]"
|
||||
ref={formRef}>
|
||||
<Form.Item
|
||||
className="p-0 bg-transparent rounded border-none"
|
||||
name="username"
|
||||
rules={[{ required: true, message: $t('请输入账号') ,whitespace:true }]}
|
||||
>
|
||||
<Input
|
||||
className="w-[350px] h-[40px] login-input"
|
||||
placeholder={$t("账号")}
|
||||
autoComplete="on"
|
||||
autoFocus
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
className="p-0 bg-transparent rounded border-none "
|
||||
name="password"
|
||||
rules={[{ required: true, message: $t('请输入密码') }]}
|
||||
>
|
||||
<Input.Password
|
||||
className="w-[350px] h-[40px] login-input"
|
||||
placeholder={$t("密码")}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
className="p-0 bg-transparent rounded border-none "
|
||||
>
|
||||
<Button loading={loading} className="h-[40px] mt-mbase w-full inline-flex justify-center items-center" type="primary" htmlType="submit">
|
||||
{$t('登录')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
{
|
||||
allowGuest && <>
|
||||
<Divider />
|
||||
|
||||
<Form.Item
|
||||
className="p-0 bg-transparent rounded border-none mb-0"
|
||||
>
|
||||
<Button loading={loading} className="h-[40px] w-full inline-flex justify-center items-center" type="default" onClick={loginAsGuest}>
|
||||
{$t('访客模式')} <Tooltip title={$t('您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。')}><Icon icon="ic:baseline-help" height={18} width={18} /></Tooltip>
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
}
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col items-center mt-[46px] text-SECOND_TEXT">
|
||||
<p className="leading-[28px]">
|
||||
{$t('Version (0)-(1)',[state?.version,state?.updateDate])}, {$t(state?.powered || '-')}
|
||||
</p>
|
||||
<LanguageSetting mode="light"/>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<section className="flex flex-col items-center mt-[46px] text-SECOND_TEXT">
|
||||
<p className="leading-[28px]">
|
||||
{$t('Version (0)-(1)', [state?.version, state?.updateDate])}, {$t(state?.powered || '-')}
|
||||
</p>
|
||||
<LanguageSetting mode="light" />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Login;
|
||||
export default Login
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import InsidePage from '@common/components/aoplatform/InsidePage'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { $t } from '@common/locales'
|
||||
import { App, Button, Form, Input, Row, Select, Switch } from 'antd'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
type AuthSetting = {
|
||||
config: {
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
}
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
type AuthFieldType = {
|
||||
authType: string
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
const Auth = () => {
|
||||
const { message } = App.useApp()
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
const [, forceUpdate] = useState<unknown>(null)
|
||||
const { state } = useGlobalContext()
|
||||
const [thirdPartyDrivers, setThirdPartyDrivers] = useState<{ label: string; value: string }[]>([])
|
||||
useEffect(() => {
|
||||
forceUpdate({})
|
||||
}, [state.language])
|
||||
const onFinish = () => {
|
||||
form.validateFields().then((value) => {
|
||||
return fetchData<BasicResponse<null>>(`account/third/${value.authType}`, {
|
||||
method: 'POST',
|
||||
eoBody: {
|
||||
enable: value.enabled,
|
||||
config: {
|
||||
client_id: value.clientId,
|
||||
client_secret: value.clientSecret
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return Promise.resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
return Promise.reject(errorInfo)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第三方授权列表
|
||||
*/
|
||||
const getThirdPartyAuthList = () => {
|
||||
fetchData<
|
||||
BasicResponse<{
|
||||
drivers: {
|
||||
name: string
|
||||
value: string
|
||||
}[]
|
||||
}>
|
||||
>('account/third', {
|
||||
method: 'GET',
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setThirdPartyDrivers(data.drivers.map((item: any) => ({ label: item.name, value: item.value })))
|
||||
if (data.drivers.length) {
|
||||
form.setFieldValue('authType', data.drivers[0].value)
|
||||
getThirdPartyAuthSetting()
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取第三方授权配置
|
||||
*/
|
||||
const getThirdPartyAuthSetting = () => {
|
||||
fetchData<BasicResponse<{ info: AuthSetting }>>(`account/third/${form.getFieldValue('authType')}`, {
|
||||
method: 'GET',
|
||||
eoTransformKeys: ['client_id', 'client_secret']
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
form.setFieldsValue({
|
||||
clientId: data.driver?.config?.clientId || '',
|
||||
clientSecret: data.driver?.config?.clientSecret || '',
|
||||
enabled: data.driver?.enable || false
|
||||
})
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getThirdPartyAuthList()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<InsidePage pageTitle={$t('鉴权')} showBorder={false} contentClassName="pr-PAGE_INSIDE_X" scrollPage={false} description={$t("系统用户账号登录授权配置")}>
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
layout="vertical"
|
||||
labelAlign="left"
|
||||
scrollToFirstError
|
||||
form={form}
|
||||
className={`mx-auto`}
|
||||
name="authConfig"
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item<AuthFieldType>
|
||||
label={$t('授权类型')}
|
||||
name="authType"
|
||||
rules={[{ required: true, message: $t('请选择授权类型') }]}
|
||||
>
|
||||
<Select className="w-INPUT_NORMAL" placeholder={$t('请选择授权类型')} onChange={getThirdPartyAuthSetting} options={thirdPartyDrivers} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AuthFieldType>
|
||||
label={$t('APP ID')}
|
||||
name="clientId"
|
||||
rules={[{ required: true, whitespace: true, message: $t('请输入APP ID') }]}
|
||||
extra={$t('APP ID 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上')}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} autoComplete="off" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AuthFieldType>
|
||||
label={$t('APP Secret')}
|
||||
name="clientSecret"
|
||||
rules={[{ required: true, whitespace: true, message: $t('请输入APP Secret') }]}
|
||||
extra={$t('APP Secret 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上')}
|
||||
>
|
||||
<Input.Password className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} autoComplete="new-password" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AuthFieldType> label={$t('启用授权')} name="enabled" valuePropName="checked">
|
||||
<Switch checkedChildren={$t('启用')} unCheckedChildren={$t('停用')} />
|
||||
</Form.Item>
|
||||
|
||||
<Row className="mb-[10px]">
|
||||
<WithPermission access="system.devops.system_setting.edit">
|
||||
<Button type="primary" htmlType="submit">
|
||||
{$t('保存')}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
</Row>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</InsidePage>
|
||||
)
|
||||
}
|
||||
|
||||
export default Auth
|
||||
@@ -50,17 +50,23 @@ type ServiceApiKeyList = {
|
||||
expired: number
|
||||
}>
|
||||
}
|
||||
|
||||
type ConsumerParamsType = {
|
||||
consumerId: string
|
||||
teamId: string
|
||||
}
|
||||
export interface IntegrationAIContainerRef {
|
||||
getServiceKeysList: () => void;
|
||||
}
|
||||
export interface IntegrationAIContainerProps {
|
||||
type: 'global' | 'service'
|
||||
type: 'global' | 'service' | 'consumer'
|
||||
handleToolsChange: (value: Tool[]) => void
|
||||
customClassName?: string
|
||||
service?: ServiceDetailType
|
||||
serviceId?: string
|
||||
currentTab?: string
|
||||
openModal?: (type: 'apply') => void
|
||||
consumerParams?: ConsumerParamsType
|
||||
}
|
||||
export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, IntegrationAIContainerProps>(
|
||||
({
|
||||
@@ -69,8 +75,9 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
customClassName,
|
||||
service,
|
||||
serviceId,
|
||||
currentTab,
|
||||
openModal
|
||||
currentTab,
|
||||
openModal,
|
||||
consumerParams
|
||||
}: IntegrationAIContainerProps, ref) => {
|
||||
/** 当前激活的标签 */
|
||||
const [activeTab, setActiveTab] = useState(type === 'service' ? 'openApi' : 'mcp')
|
||||
@@ -180,7 +187,35 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
message.error(errorInfo?.toString() || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消费者 MCP 配置
|
||||
* @returns
|
||||
*/
|
||||
const getConsumerMcpConfig = () => {
|
||||
fetchData<BasicResponse<null>>('app/mcp/config', {
|
||||
method: 'GET',
|
||||
eoParams: { app: consumerParams?.consumerId, team: consumerParams?.teamId }
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg, data } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setTabContent((prevTabContent) => ({
|
||||
...prevTabContent,
|
||||
mcp: {
|
||||
...prevTabContent.mcp,
|
||||
configContent: data.config || ''
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo?.toString() || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -191,6 +226,10 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
navigator('/mcpKey')
|
||||
}
|
||||
|
||||
const dropAuthPage = () => {
|
||||
navigator(`/consumer/${consumerParams?.teamId}/inside/${consumerParams?.consumerId}/authorization`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局 API Key 列表
|
||||
*/
|
||||
@@ -217,7 +256,7 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
message.error(errorInfo?.toString() || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -229,12 +268,12 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
}))
|
||||
|
||||
/**
|
||||
* 获取服务 API Key 列表
|
||||
* 获取 API Key 列表
|
||||
*/
|
||||
const getServiceKeysList = () => {
|
||||
const getServiceKeysList = (consumerId?: string) => {
|
||||
fetchData<BasicResponse<null>>(`my/app/apikeys`, {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId }
|
||||
eoParams: consumerId ? { app: consumerId } : { service: serviceId }
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg, data } = response
|
||||
@@ -258,7 +297,7 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
message.error(errorInfo || $t(RESPONSE_TIPS.error))
|
||||
message.error(errorInfo?.toString() || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -345,6 +384,10 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
getGlobalMcpConfig()
|
||||
setMcpServerUrl('mcp/global/sse')
|
||||
getGlobalKeysList()
|
||||
} else if (type === 'consumer'){
|
||||
getConsumerMcpConfig()
|
||||
setMcpServerUrl(`mcp/app/${consumerParams?.consumerId}/sse`)
|
||||
getServiceKeysList(consumerParams?.consumerId)
|
||||
} else {
|
||||
service?.basic.enableMcp && setMcpServerUrl(`mcp/service/${serviceId}/sse`)
|
||||
getServiceKeysList()
|
||||
@@ -362,6 +405,7 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
useEffect(() => {
|
||||
initTabsData()
|
||||
type === 'global' && getGlobalMcpConfig()
|
||||
type === 'consumer' && getConsumerMcpConfig()
|
||||
}, [state.language])
|
||||
/**
|
||||
* 切换标签
|
||||
@@ -408,7 +452,7 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
}
|
||||
connectMcpServer()
|
||||
}
|
||||
}, [mcpServerUrl, ...(type === 'global' ? [state.language] : [])])
|
||||
}, [mcpServerUrl, ...(type === 'global' || type === 'consumer' ? [state.language] : [])])
|
||||
/**
|
||||
* 获取 MCP tools
|
||||
*/
|
||||
@@ -452,7 +496,7 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{type === 'service' && !apiKeyList.length ? (
|
||||
{(type === 'service' || type === 'consumer') && !apiKeyList.length ? (
|
||||
<>
|
||||
<Card
|
||||
style={{ borderRadius: '10px' }}
|
||||
@@ -461,12 +505,23 @@ export const IntegrationAIContainer = forwardRef<IntegrationAIContainerRef, Inte
|
||||
body: 'p-[10px]'
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center py-3">
|
||||
<span className="text-[14px] mb-5">{$t('请先订阅该服务')}</span>
|
||||
<Button type="primary" onClick={() => openModal?.('apply')}>
|
||||
{$t('申请')}
|
||||
</Button>
|
||||
</div>
|
||||
{
|
||||
type === 'service' ? (
|
||||
<div className="flex flex-col items-center justify-center py-3">
|
||||
<span className="text-[14px] mb-5">{$t('请先订阅该服务')}</span>
|
||||
<Button type="primary" onClick={() => openModal?.('apply')}>
|
||||
{$t('申请')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-3">
|
||||
<span className="text-[14px] mb-5">{$t('未配置 API Key')}</span>
|
||||
<Button type="primary" onClick={() => dropAuthPage()}>
|
||||
{$t('配置')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Card>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -14,7 +14,7 @@ export const MemberDropdownModal = forwardRef<MemberDropdownModalHandle,MemberDr
|
||||
const {fetchData} = useFetch()
|
||||
const [departmentList, setDepartmentList] = useState<DepartmentListItem[]>([])
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
const [disableEditMemberData] = useState<boolean>(entity?.from === 'feishu')
|
||||
const save:()=>Promise<boolean | string> = ()=>{
|
||||
let url:string
|
||||
let method:string
|
||||
@@ -182,27 +182,28 @@ export const MemberDropdownModal = forwardRef<MemberDropdownModalHandle,MemberDr
|
||||
name="name"
|
||||
rules={[{required: true,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" disabled={type ==='editMember'} placeholder={$t(PLACEHOLDER.input)}/>
|
||||
<Input className="w-INPUT_NORMAL" disabled={disableEditMemberData || type ==='editMember'} placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
<Form.Item<MemberDropdownModalFieldType>
|
||||
label={$t("邮箱")}
|
||||
name="email"
|
||||
rules={[{required: true,whitespace:true },{type:"email",message: $t(VALIDATE_MESSAGE.email)}]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
<Input className="w-INPUT_NORMAL" disabled={disableEditMemberData} placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
<Form.Item<MemberDropdownModalFieldType>
|
||||
label={$t("密码")}
|
||||
name="password"
|
||||
rules={[{required: type === 'addMember',whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
<Input disabled={disableEditMemberData} className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
<Form.Item<MemberDropdownModalFieldType>
|
||||
label={$t("部门")}
|
||||
name="departmentIds"
|
||||
>
|
||||
<TreeSelect
|
||||
disabled={disableEditMemberData}
|
||||
className="w-INPUT_NORMAL"
|
||||
fieldNames={{label:'name',value:'id',children:'children'}}
|
||||
showSearch
|
||||
|
||||
@@ -95,14 +95,15 @@ const AddToDepartment = forwardRef<AddToDepartmentHandle, AddToDepartmentProps>(
|
||||
treeData?.map((x: DataNode) => ({
|
||||
...x,
|
||||
name: $t((x as unknown as { name: string }).name),
|
||||
checkable: false,
|
||||
children: x.children?.map(y => ({ ...y, checkable: false }))
|
||||
checkable: false, // 根节点不可选中
|
||||
children: x.children?.map(y => ({ ...y, checkable: true })) // 子节点可以选中
|
||||
})),
|
||||
[state.language, treeData]
|
||||
)
|
||||
|
||||
const onCheck: TreeProps['onCheck'] = (checkedKeys: string[]) => {
|
||||
setSelectedKeys(checkedKeys.checked)
|
||||
const onCheck: TreeProps['onCheck'] = (checkedKeys, info) => {
|
||||
const selectedIds = Array.isArray(checkedKeys) ? checkedKeys : checkedKeys.checked || []
|
||||
setSelectedKeys(selectedIds)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -153,11 +154,12 @@ const MemberList = () => {
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true)
|
||||
const [tableListDataSource, setTableListDataSource] = useState<MemberTableListItem[]>([])
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
const { topGroupId, selectedDepartmentIds, refreshGroup } = useOutletContext<{
|
||||
const { topGroupId, selectedDepartmentIds, refreshGroup, refreshTableCount } = useOutletContext<{
|
||||
topGroupId: string
|
||||
departmentList: DepartmentListItem[]
|
||||
selectedDepartmentIds: string[]
|
||||
refreshGroup: () => void
|
||||
refreshTableCount: number
|
||||
}>()
|
||||
const AddMemberRef = useRef<MemberDropdownModalHandle>(null)
|
||||
const EditMemberRef = useRef<MemberDropdownModalHandle>(null)
|
||||
@@ -396,7 +398,7 @@ const MemberList = () => {
|
||||
width: 600,
|
||||
okText: $t('确认'),
|
||||
okButtonProps: {
|
||||
disabled: isActionAllowed(type)
|
||||
disabled: isActionAllowed(type) || (type === 'editMember' && entity?.from === 'feishu')
|
||||
},
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
@@ -415,6 +417,13 @@ const MemberList = () => {
|
||||
getDepartmentList()
|
||||
}, [])
|
||||
|
||||
// 监听外部刷新触发器
|
||||
useEffect(() => {
|
||||
if (refreshTableCount > 0) {
|
||||
manualReloadTable()
|
||||
}
|
||||
}, [refreshTableCount])
|
||||
|
||||
const getDepartmentList = async () => {
|
||||
setDepartmentValueEnum([])
|
||||
const { code, data, msg } = await fetchData<BasicResponse<{ department: DepartmentListItem }>>(
|
||||
|
||||
@@ -36,6 +36,11 @@ const MemberPage = ()=>{
|
||||
const [selectedDepartmentId, setSelectedDepartmentId] = useState<string>('-1')
|
||||
const {accessData,state} = useGlobalContext()
|
||||
const [refreshMemberCount, setRefreshMemberCount] = useState<number>(0)
|
||||
const [refreshTableCount, setRefreshTableCount] = useState<number>(0)
|
||||
|
||||
const refreshMemberTable = () => {
|
||||
setRefreshTableCount(prev => prev + 1)
|
||||
}
|
||||
const onSearchWordChange = (e:string)=>{
|
||||
setSearchWord(e || '')
|
||||
}
|
||||
@@ -90,7 +95,7 @@ const MemberPage = ()=>{
|
||||
case 'addChild':
|
||||
return AddChildRef.current?.save().then((res)=>{if(res === true)getDepartmentList()})
|
||||
case 'addMember':
|
||||
return AddMemberRef.current?.save().then((res)=>{if(res === true){getDepartmentList();setRefreshMemberCount(pre=>pre+1)}})
|
||||
return AddMemberRef.current?.save().then((res)=>{if(res === true){getDepartmentList();setRefreshMemberCount(pre=>pre+1);refreshMemberTable()}})
|
||||
case 'rename':
|
||||
return RenameRef.current?.save().then((res)=>{if(res === true)getDepartmentList()})
|
||||
case 'delete':
|
||||
@@ -262,7 +267,7 @@ const MemberPage = ()=>{
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 p-btnbase pr-PAGE_INSIDE_X overflow-x-hidden">
|
||||
<Outlet context={{refreshMemberCount, selectedDepartmentIds,refreshGroup:()=>getDepartmentList()}}/>
|
||||
<Outlet context={{refreshMemberCount, selectedDepartmentIds,refreshGroup:()=>getDepartmentList(), refreshTableCount}}/>
|
||||
</div>
|
||||
</div>
|
||||
</InsidePage>);
|
||||
|
||||
@@ -20,6 +20,10 @@ interface PageListRef {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 排名列表
|
||||
* @returns
|
||||
*/
|
||||
const RankingList = ({ topRankingList, serviceType }: { topRankingList: RankingListData; serviceType: 'aiService' | 'restService' }) => {
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function ManagementInsidePage() {
|
||||
const { message } = App.useApp()
|
||||
const { fetchData } = useFetch()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [activeMenu, setActiveMenu] = useState<string>('service')
|
||||
const [activeMenu, setActiveMenu] = useState<string>('authorization')
|
||||
const { appId, teamId } = useParams<RouterParams>()
|
||||
const navigateTo = useNavigate()
|
||||
const currentUrl = useLocation().pathname
|
||||
@@ -31,8 +31,9 @@ export default function ManagementInsidePage() {
|
||||
|
||||
const TENANT_MANAGEMENT_APP_MENU: MenuProps['items'] = useMemo(
|
||||
() => [
|
||||
getItem($t('订阅的服务'), 'service', undefined, undefined, undefined, 'team.application.subscription.view'),
|
||||
getItem($t('访问授权'), 'authorization', undefined, undefined, undefined, 'team.consumer.authorization.view'),
|
||||
getItem($t('MCP 服务'), 'mcp', undefined, undefined, undefined, 'team.consumer.mcp.view'),
|
||||
getItem($t('订阅的服务'), 'service', undefined, undefined, undefined, 'team.application.subscription.view'),
|
||||
getItem($t('消费者管理'), 'setting', undefined, undefined, undefined, 'team.application.application.view')
|
||||
],
|
||||
[state.language]
|
||||
@@ -55,7 +56,7 @@ export default function ManagementInsidePage() {
|
||||
}, [accessData, accessInit, TENANT_MANAGEMENT_APP_MENU])
|
||||
|
||||
useEffect(() => {
|
||||
setActiveMenu(currentUrl.split('/').pop() || 'service')
|
||||
setActiveMenu(currentUrl.split('/').pop() || 'authorization')
|
||||
}, [currentUrl])
|
||||
|
||||
const onMenuClick: MenuProps['onClick'] = (node) => {
|
||||
|
||||
@@ -336,7 +336,7 @@ export default function ServiceHubManagement() {
|
||||
setTableSearchWord={setTableSearchWord}
|
||||
editApp={(row: ServiceHubAppListItem) => {
|
||||
setAppName(row.name)
|
||||
navigateTo(`/consumer/${row.team.id}/inside/${row.id}/service`)
|
||||
navigateTo(`/consumer/${row.team.id}/inside/${row.id}/authorization`)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { $t } from '@common/locales'
|
||||
import { IntegrationAIContainer } from '@core/pages/mcpService/IntegrationAIContainer'
|
||||
import { Tool } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import McpToolsContainer from '@core/pages/mcpService/McpToolsContainer'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { RouterParams } from '@common/const/type'
|
||||
|
||||
const mcpContent = () => {
|
||||
const [tools, setTools] = useState<Tool[]>([])
|
||||
const [, forceUpdate] = useState<unknown>(null)
|
||||
const { teamId, appId } = useParams<RouterParams>()
|
||||
const { state } = useGlobalContext()
|
||||
const handleToolsChange = (value: Tool[]) => {
|
||||
setTools(value)
|
||||
}
|
||||
useEffect(() => {
|
||||
forceUpdate({})
|
||||
}, [state.language])
|
||||
return (
|
||||
<div className=" h-full pt-[32px]">
|
||||
<div className="flex items-center justify-between w-full ml-[10px] text-[18px] leading-[25px] pb-[16px]">
|
||||
<span className="font-bold">{$t('MCP 服务')}</span>
|
||||
</div>
|
||||
<div className="h-[calc(100%-41px)] flex flex-col ">
|
||||
<div className="flex mt-[10px] pr-[40px]">
|
||||
<McpToolsContainer tools={tools} />
|
||||
<IntegrationAIContainer
|
||||
consumerParams={{ consumerId: appId!, teamId: teamId! }}
|
||||
type={'consumer'}
|
||||
handleToolsChange={handleToolsChange}
|
||||
></IntegrationAIContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default mcpContent
|
||||
@@ -9,13 +9,13 @@ require (
|
||||
github.com/eolinker/eosc v0.18.3
|
||||
github.com/eolinker/go-common v1.1.7
|
||||
github.com/gabriel-vasile/mimetype v1.4.4
|
||||
github.com/getkin/kin-openapi v0.127.0
|
||||
github.com/getkin/kin-openapi v0.132.0
|
||||
github.com/gin-contrib/gzip v1.0.1
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0
|
||||
github.com/mark3labs/mcp-go v0.17.0
|
||||
github.com/mark3labs/mcp-go v0.33.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/nsqio/go-nsq v1.1.0
|
||||
github.com/ollama/ollama v0.5.8
|
||||
@@ -46,7 +46,6 @@ require (
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
|
||||
github.com/invopop/yaml v0.3.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
@@ -59,10 +58,13 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/oapi-codegen/runtime v1.0.0 // indirect
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/redis/go-redis/v9 v9.5.3 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
|
||||
@@ -34,10 +34,12 @@ github.com/eolinker/eosc v0.18.3 h1:3IK5HkAPnJRfLbQ0FR7kWsZr6Y/OiqqGazvN1q2BL5A=
|
||||
github.com/eolinker/eosc v0.18.3/go.mod h1:O9PQQXFCpB6fjHf+oFt/LN6EOAv779ItbMixMKCfTfk=
|
||||
github.com/eolinker/go-common v1.1.7 h1:bi7wDmlCYQGjS3k8Bz/o+Mo9aMJAzmPsBLXWurxPfwk=
|
||||
github.com/eolinker/go-common v1.1.7/go.mod h1:Kb/jENMN1mApnodvRgV4YwO9FJby1Jkt2EUjrBjvSX4=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
|
||||
github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY=
|
||||
github.com/getkin/kin-openapi v0.127.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM=
|
||||
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
|
||||
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
|
||||
@@ -78,8 +80,6 @@ github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjw
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
||||
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
|
||||
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
@@ -101,8 +101,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mark3labs/mcp-go v0.17.0 h1:5Ps6T7qXr7De/2QTqs9h6BKeZ/qdeUeGrgM5lPzi930=
|
||||
github.com/mark3labs/mcp-go v0.17.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
|
||||
github.com/mark3labs/mcp-go v0.33.0 h1:naxhjnTIs/tyPZmWUZFuG0lDmdA6sUyYGGf3gsHvTCc=
|
||||
github.com/mark3labs/mcp-go v0.33.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
@@ -118,6 +118,10 @@ github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=
|
||||
github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY=
|
||||
github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
|
||||
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/ollama/ollama v0.5.8 h1:b2S6YdZ18/ntCsWzoy/HmB3BHGW4GX0Qp7RARrJtJXU=
|
||||
github.com/ollama/ollama v0.5.8/go.mod h1:ibdmDvb/TjKY1OArBWIazL3pd1DHTk8eG2MMjEkWhiI=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
@@ -134,6 +138,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
|
||||
@@ -33,12 +33,12 @@ func TestLoki(t *testing.T) {
|
||||
// headers["Content-Type"] = "application/json"
|
||||
// headers["X-Scope-OrgID"] = "tenant1"
|
||||
// queries := url.Values{}
|
||||
// queries.Set("query", "{cluster=\"apinto\"} | json | request_id = `c9f6b19c-7dfe-496b-9b39-4d049232fe95`")
|
||||
// queries.SetMCPServer("query", "{cluster=\"apinto\"} | json | request_id = `c9f6b19c-7dfe-496b-9b39-4d049232fe95`")
|
||||
// now := time.Now()
|
||||
// start := now.Add(-time.Hour * 24 * 30)
|
||||
// queries.Set("start", strconv.FormatInt(start.UnixNano(), 10))
|
||||
// queries.Set("end", strconv.FormatInt(now.UnixNano(), 10))
|
||||
// queries.Set("limit", "100")
|
||||
// queries.SetMCPServer("start", strconv.FormatInt(start.UnixNano(), 10))
|
||||
// queries.SetMCPServer("end", strconv.FormatInt(now.UnixNano(), 10))
|
||||
// queries.SetMCPServer("limit", "100")
|
||||
// a := time.Now()
|
||||
// result, err := send[LogInfo](http.MethodGet, "http://localhost:3100/loki/api/v1/query_range", headers, queries, "")
|
||||
// if err != nil {
|
||||
@@ -57,8 +57,8 @@ func TestLoki(t *testing.T) {
|
||||
// headers["Content-Type"] = "application/json"
|
||||
// headers["X-Scope-OrgID"] = "tenant1"
|
||||
// queries := url.Values{}
|
||||
// //queries.Set("query", "sum(count_over_time({cluster=\"apinto\"}[24h])) by (strategy)")
|
||||
// queries.Set("query", "sum(count_over_time({cluster=\"apinto\"}[24h]))")
|
||||
// //queries.SetMCPServer("query", "sum(count_over_time({cluster=\"apinto\"}[24h])) by (strategy)")
|
||||
// queries.SetMCPServer("query", "sum(count_over_time({cluster=\"apinto\"}[24h]))")
|
||||
// result, err := send[LogCount](http.MethodGet, "http://localhost:3100/loki/api/v1/query", headers, queries, "")
|
||||
// if err != nil {
|
||||
// t.Fatalf("failed to send request: %v", err)
|
||||
@@ -75,12 +75,12 @@ func TestLoki(t *testing.T) {
|
||||
// headers["Content-Type"] = "application/json"
|
||||
// headers["X-Scope-OrgID"] = "tenant1"
|
||||
// queries := url.Values{}
|
||||
// queries.Set("query", "{cluster=\"apinto\"} | json | strategy=\"03899736-5d79-4f26-bd6a-c312a5880780\"")
|
||||
// queries.SetMCPServer("query", "{cluster=\"apinto\"} | json | strategy=\"03899736-5d79-4f26-bd6a-c312a5880780\"")
|
||||
// now := time.Now()
|
||||
// start := now.Add(-time.Hour * 24 * 30)
|
||||
// queries.Set("start", strconv.FormatInt(start.UnixNano(), 10))
|
||||
// queries.Set("end", strconv.FormatInt(now.UnixNano(), 10))
|
||||
// queries.Set("limit", "1")
|
||||
// queries.SetMCPServer("start", strconv.FormatInt(start.UnixNano(), 10))
|
||||
// queries.SetMCPServer("end", strconv.FormatInt(now.UnixNano(), 10))
|
||||
// queries.SetMCPServer("limit", "1")
|
||||
// now = time.Now()
|
||||
// result, err := send[map[string]interface{}](http.MethodGet, "http://localhost:3100/loki/api/v1/query_range", headers, queries, "")
|
||||
// t.LogItem(time.Now().Sub(now))
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package mcp_server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var client = http.Client{}
|
||||
|
||||
type Position string
|
||||
|
||||
const (
|
||||
PositionHeader Position = "header"
|
||||
PositionBody Position = "body"
|
||||
PositionQuery Position = "query"
|
||||
PositionPath Position = "path"
|
||||
)
|
||||
|
||||
type ContentType string
|
||||
|
||||
const (
|
||||
ContentTypeJSON ContentType = "application/json"
|
||||
ContentTypeXML ContentType = "application/xml"
|
||||
ContentTypeHTML ContentType = "text/html"
|
||||
ContentTypeText ContentType = "text/plain"
|
||||
ContentTypeForm ContentType = "application/x-www-form-urlencoded"
|
||||
ContentTypeFile ContentType = "multipart/form-data"
|
||||
)
|
||||
|
||||
func NewParam(position Position, required bool, description string) *Param {
|
||||
return &Param{position: position, required: required, description: description}
|
||||
}
|
||||
|
||||
type Param struct {
|
||||
position Position
|
||||
required bool
|
||||
description string
|
||||
}
|
||||
|
||||
func (p *Param) Description() string {
|
||||
return p.description
|
||||
}
|
||||
|
||||
func (p *Param) Required() bool {
|
||||
return p.required
|
||||
}
|
||||
|
||||
type BodyParam struct {
|
||||
contentType ContentType
|
||||
params map[string]interface{}
|
||||
}
|
||||
|
||||
func NewBodyParam(contentType string) *BodyParam {
|
||||
t := ContentType(contentType)
|
||||
if t == "" {
|
||||
t = ContentTypeJSON
|
||||
}
|
||||
return &BodyParam{contentType: t}
|
||||
}
|
||||
|
||||
func (p *BodyParam) Set(k string, v interface{}) {
|
||||
if p.params == nil {
|
||||
p.params = make(map[string]interface{})
|
||||
}
|
||||
p.params[k] = v
|
||||
}
|
||||
|
||||
func (p *BodyParam) Encode() (string, error) {
|
||||
switch p.contentType {
|
||||
case ContentTypeJSON:
|
||||
data, err := json.Marshal(p.params)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("body param encode error: %w", err)
|
||||
}
|
||||
return string(data), nil
|
||||
case ContentTypeForm, ContentTypeFile:
|
||||
data := url.Values{}
|
||||
for k, v := range p.params {
|
||||
data.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
return data.Encode(), nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported content type: %s", p.contentType)
|
||||
}
|
||||
}
|
||||
+213
-34
@@ -4,10 +4,12 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
|
||||
"github.com/eolinker/eosc"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -18,55 +20,120 @@ var (
|
||||
|
||||
func NewServer() *Server {
|
||||
return &Server{
|
||||
sseServers: eosc.BuildUntyped[string, *server.SSEServer](),
|
||||
servers: make(map[string]*Handler),
|
||||
}
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
sseServers eosc.Untyped[string, *server.SSEServer]
|
||||
servers map[string]*Handler
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *Server) Set(path string, sseServer *server.SSEServer) {
|
||||
s.sseServers.Set(path, sseServer)
|
||||
type Handler struct {
|
||||
*server.MCPServer
|
||||
handlers map[string]http.Handler
|
||||
}
|
||||
|
||||
func (s *Server) Del(path string) {
|
||||
s.sseServers.Del(path)
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
sseServer, has := s.sseServers.Get(trimPath(r.URL.Path))
|
||||
if has {
|
||||
sseServer.ServeHTTP(w, r)
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")
|
||||
if strings.HasSuffix(r.URL.Path, "/mcp") {
|
||||
h.handlers["openapi-stream"].ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(r.URL.Path, "/api") {
|
||||
h.handlers["api-sse"].ServeHTTP(w, r)
|
||||
return
|
||||
} else if strings.HasPrefix(r.URL.Path, "/openapi") {
|
||||
h.handlers["openapi-sse"].ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
func trimPath(path string) string {
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
path = strings.TrimSuffix(path, "/message")
|
||||
path = strings.TrimSuffix(path, "/sse")
|
||||
return path
|
||||
}
|
||||
|
||||
func SetSSEServer(sid string, name string, version string, tools ...ITool) {
|
||||
s := server.NewMCPServer(name, version)
|
||||
for _, tool := range tools {
|
||||
tool.RegisterMCP(s)
|
||||
func (s *Server) Set(id string, ser *server.MCPServer) {
|
||||
s.locker.Lock()
|
||||
defer s.locker.Unlock()
|
||||
tmp := &Handler{
|
||||
MCPServer: ser,
|
||||
handlers: make(map[string]http.Handler),
|
||||
}
|
||||
apiPath := fmt.Sprintf("/api/v1/%s/%s", ServiceBasePath, sid)
|
||||
openAPIPath := fmt.Sprintf("/openapi/v1/%s/%s", ServiceBasePath, sid)
|
||||
mcpServer.Set(apiPath, server.NewSSEServer(s, server.WithBasePath(apiPath)))
|
||||
mcpServer.Set(openAPIPath, server.NewSSEServer(s, server.WithBasePath(openAPIPath)))
|
||||
tmp.handlers["api-sse"] = server.NewSSEServer(ser, server.WithStaticBasePath(fmt.Sprintf("/api/v1/%s/%s", ServiceBasePath, id)))
|
||||
tmp.handlers["openapi-sse"] = server.NewSSEServer(ser, server.WithStaticBasePath(fmt.Sprintf("/openapi/v1/%s/%s", ServiceBasePath, id)))
|
||||
tmp.handlers["openapi-stream"] = server.NewStreamableHTTPServer(ser, server.WithEndpointPath(fmt.Sprintf("/openapi/v1/%s/%s/mcp", ServiceBasePath, id)))
|
||||
s.servers[id] = tmp
|
||||
|
||||
}
|
||||
|
||||
func DelSSEServer(sid string) {
|
||||
apiPath := fmt.Sprintf("/api/v1/%s/%s", ServiceBasePath, sid)
|
||||
openAPIPath := fmt.Sprintf("/openapi/v1/%s/%s", ServiceBasePath, sid)
|
||||
mcpServer.Del(apiPath)
|
||||
mcpServer.Del(openAPIPath)
|
||||
func (s *Server) Del(id string) {
|
||||
s.locker.Lock()
|
||||
defer s.locker.Unlock()
|
||||
delete(s.servers, id)
|
||||
}
|
||||
|
||||
func (s *Server) Get(id string) (*Handler, bool) {
|
||||
s.locker.RLock()
|
||||
defer s.locker.RUnlock()
|
||||
ser, has := s.servers[id]
|
||||
if !has {
|
||||
return nil, false
|
||||
}
|
||||
m := &Handler{
|
||||
MCPServer: ser.MCPServer,
|
||||
handlers: make(map[string]http.Handler),
|
||||
}
|
||||
for k, v := range ser.handlers {
|
||||
m.handlers[k] = v
|
||||
}
|
||||
|
||||
return m, true
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
sid, err := genPath(r.URL.Path)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
ser, has := s.Get(sid)
|
||||
if has {
|
||||
ser.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
func genPath(path string) (sid string, err error) {
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
ps := strings.Split(path, "/")
|
||||
if len(ps) < 2 {
|
||||
err = fmt.Errorf("invalid path: %s", path)
|
||||
return
|
||||
}
|
||||
sid = ps[len(ps)-2]
|
||||
return
|
||||
}
|
||||
|
||||
func SetServer(sid string, name string, version string, tools ...ITool) {
|
||||
ser, has := mcpServer.Get(sid)
|
||||
if !has {
|
||||
mcpServer.Set(sid, server.NewMCPServer(name, version, server.WithToolCapabilities(true)))
|
||||
ser, has = mcpServer.Get(sid)
|
||||
if !has {
|
||||
return
|
||||
}
|
||||
}
|
||||
ts := make([]server.ServerTool, 0, len(tools))
|
||||
for _, tool := range tools {
|
||||
ts = append(ts, tool.Tool())
|
||||
}
|
||||
ser.SetTools(ts...)
|
||||
}
|
||||
|
||||
func DelServer(sid string) {
|
||||
mcpServer.Del(sid)
|
||||
}
|
||||
|
||||
func ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -76,3 +143,115 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func DefaultMCPServer() *Server {
|
||||
return mcpServer
|
||||
}
|
||||
|
||||
func SetServerByOpenapi(sid, name, version, content string) error {
|
||||
mcpInfo, err := ConvertMCPFromOpenAPI3Data([]byte(content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert mcp from openapi3 data error: %w", err)
|
||||
}
|
||||
tools := make([]ITool, 0, len(mcpInfo.Apis))
|
||||
for _, a := range mcpInfo.Apis {
|
||||
toolOptions := make([]mcp.ToolOption, 0, len(a.Params)+2)
|
||||
toolOptions = append(toolOptions, mcp.WithDescription(a.Description))
|
||||
params := make(map[string]*Param)
|
||||
for _, v := range a.Params {
|
||||
params[v.Name] = NewParam(Position(v.In), v.Required, v.Description)
|
||||
options := make([]mcp.PropertyOption, 0, 2)
|
||||
if v.Required {
|
||||
options = append(options, mcp.Required())
|
||||
}
|
||||
options = append(options, mcp.Description(v.Description))
|
||||
toolOptions = append(toolOptions, mcp.WithString(v.Name, options...))
|
||||
}
|
||||
if a.Body != nil {
|
||||
type Schema struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Properties map[string]interface{} `mapstructure:"properties"`
|
||||
Items interface{} `mapstructure:"items"`
|
||||
Required interface{} `mapstructure:"required"`
|
||||
}
|
||||
var tmp Schema
|
||||
err = mapstructure.Decode(a.Body, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
required := map[string]struct{}{}
|
||||
switch t := tmp.Required.(type) {
|
||||
case []interface{}:
|
||||
for _, v := range t {
|
||||
i, ok := v.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
required[i] = struct{}{}
|
||||
}
|
||||
}
|
||||
for k, v := range tmp.Properties {
|
||||
description := ""
|
||||
typ := "string"
|
||||
isRequired := false
|
||||
if _, ok := required[k]; ok {
|
||||
isRequired = true
|
||||
}
|
||||
var props map[string]interface{}
|
||||
var items interface{}
|
||||
switch t := v.(type) {
|
||||
case map[string]interface{}:
|
||||
if m, ok := t["type"]; ok {
|
||||
n, ok := m.(string)
|
||||
if ok {
|
||||
typ = n
|
||||
}
|
||||
}
|
||||
if m, ok := t["description"]; ok {
|
||||
n, ok := m.(string)
|
||||
if ok {
|
||||
description = n
|
||||
}
|
||||
}
|
||||
switch typ {
|
||||
case "array":
|
||||
if m, ok := t["items"]; ok {
|
||||
items = m
|
||||
}
|
||||
case "object":
|
||||
if m, ok := t["properties"]; ok {
|
||||
n, ok := m.(map[string]interface{})
|
||||
if ok {
|
||||
props = n
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params[k] = NewParam(PositionBody, isRequired, description)
|
||||
options := make([]mcp.PropertyOption, 0, 3)
|
||||
options = append(options, mcp.Description(description))
|
||||
if props != nil {
|
||||
options = append(options, mcp.Properties(props))
|
||||
}
|
||||
if items != nil {
|
||||
options = append(options, mcp.Items(items))
|
||||
}
|
||||
switch typ {
|
||||
case "string":
|
||||
toolOptions = append(toolOptions, mcp.WithString(k, options...))
|
||||
case "integer", "number", "float":
|
||||
toolOptions = append(toolOptions, mcp.WithNumber(k, options...))
|
||||
case "boolean":
|
||||
toolOptions = append(toolOptions, mcp.WithBoolean(k, options...))
|
||||
case "array":
|
||||
toolOptions = append(toolOptions, mcp.WithArray(k, options...))
|
||||
case "object":
|
||||
toolOptions = append(toolOptions, mcp.WithObject(k, options...))
|
||||
default:
|
||||
return fmt.Errorf("unsupported type: %s", typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tools = append(tools, NewTool(a.Summary, a.Path, a.Method, a.ContentType, params, toolOptions...))
|
||||
}
|
||||
SetServer(sid, name, version, tools...)
|
||||
return nil
|
||||
}
|
||||
|
||||
+44
-60
@@ -2,7 +2,6 @@ package mcp_server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -16,36 +15,38 @@ import (
|
||||
)
|
||||
|
||||
type ITool interface {
|
||||
RegisterMCP(s *server.MCPServer)
|
||||
Tool() server.ServerTool
|
||||
}
|
||||
|
||||
const (
|
||||
MCPBody = "Body"
|
||||
MCPHeader = "Header"
|
||||
MCPQuery = "Query"
|
||||
MCPPath = "Path"
|
||||
)
|
||||
|
||||
type Tool struct {
|
||||
name string
|
||||
url string
|
||||
method string
|
||||
contentType string
|
||||
params map[string]*Param
|
||||
opts []mcp.ToolOption
|
||||
}
|
||||
|
||||
func NewTool(name string, uri string, method string, contentType string, opts ...mcp.ToolOption) ITool {
|
||||
func (t *Tool) Tool() server.ServerTool {
|
||||
return server.ServerTool{
|
||||
Tool: mcp.NewTool(t.name, t.opts...),
|
||||
Handler: generateInvokeTool(t.url, t.method, t.contentType, t.params),
|
||||
}
|
||||
}
|
||||
|
||||
func NewTool(name string, uri string, method string, contentType string, params map[string]*Param, opts ...mcp.ToolOption) ITool {
|
||||
return &Tool{
|
||||
name: name,
|
||||
url: uri,
|
||||
method: method,
|
||||
contentType: contentType,
|
||||
params: params,
|
||||
opts: opts,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tool) RegisterMCP(s *server.MCPServer) {
|
||||
s.AddTool(mcp.NewTool(t.name, t.opts...), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
func generateInvokeTool(path string, method string, contentType string, params map[string]*Param) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
invokeAddress := utils.GatewayInvoke(ctx)
|
||||
if invokeAddress == "" {
|
||||
return nil, fmt.Errorf("invoke address is empty")
|
||||
@@ -58,69 +59,54 @@ func (t *Tool) RegisterMCP(s *server.MCPServer) {
|
||||
u.Scheme = "http"
|
||||
}
|
||||
|
||||
path := t.url
|
||||
queries := url.Values{}
|
||||
headers := make(map[string]string)
|
||||
body := ""
|
||||
for k, v := range request.Params.Arguments {
|
||||
if k == "Body" {
|
||||
switch a := v.(type) {
|
||||
case string:
|
||||
body = a
|
||||
case map[string]interface{}:
|
||||
switch t.contentType {
|
||||
case "application/json":
|
||||
tmp, _ := json.Marshal(a)
|
||||
body = string(tmp)
|
||||
case "application/x-www-form-urlencoded":
|
||||
bodyValue := url.Values{}
|
||||
for kk, vv := range a {
|
||||
bodyValue.Set(kk, fmt.Sprintf("%v", vv))
|
||||
}
|
||||
body = bodyValue.Encode()
|
||||
bodyParam := NewBodyParam(contentType)
|
||||
for k, p := range params {
|
||||
vv, ok := request.GetArguments()[k]
|
||||
if !ok && p.required {
|
||||
return nil, fmt.Errorf("param %s is required", k)
|
||||
}
|
||||
if p.position == PositionHeader || p.position == PositionQuery || p.position == PositionPath {
|
||||
v, ok := vv.(string)
|
||||
if !ok || v == "<nil>" {
|
||||
if p.required {
|
||||
return nil, fmt.Errorf("param %s is required", k)
|
||||
}
|
||||
default:
|
||||
tmp, _ := json.Marshal(a)
|
||||
body = string(tmp)
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
tmp, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case MCPHeader:
|
||||
for kk, vv := range tmp {
|
||||
headers[kk] = fmt.Sprintf("%v", vv)
|
||||
}
|
||||
|
||||
case MCPQuery:
|
||||
for kk, vv := range tmp {
|
||||
queries.Set(kk, fmt.Sprintf("%v", vv))
|
||||
}
|
||||
case MCPPath:
|
||||
for kk, vv := range tmp {
|
||||
p, ok := vv.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid path %s", v)
|
||||
}
|
||||
path = strings.Replace(path, fmt.Sprintf("{%s}", kk), p, -1)
|
||||
switch p.position {
|
||||
case PositionPath:
|
||||
path = strings.ReplaceAll(path, "{"+k+"}", fmt.Sprintf("%v", vv))
|
||||
case PositionQuery:
|
||||
queries.Set(k, fmt.Sprintf("%v", vv))
|
||||
case PositionHeader:
|
||||
headers[k] = fmt.Sprintf("%v", vv)
|
||||
case PositionBody:
|
||||
if vv == nil {
|
||||
continue
|
||||
}
|
||||
bodyParam.Set(k, vv)
|
||||
}
|
||||
}
|
||||
bodyData, err := bodyParam.Encode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Path = path
|
||||
u.RawQuery = queries.Encode()
|
||||
|
||||
req, err := http.NewRequest(t.method, u.String(), strings.NewReader(body))
|
||||
req, err := http.NewRequest(method, u.String(), strings.NewReader(bodyData))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
if t.contentType != "" {
|
||||
req.Header.Set("Content-Type", t.contentType)
|
||||
if contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
apikey := utils.Label(ctx, "apikey")
|
||||
if apikey != "" {
|
||||
@@ -141,7 +127,5 @@ func (t *Tool) RegisterMCP(s *server.MCPServer) {
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(string(d)), nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var client = http.Client{}
|
||||
|
||||
+32
-12
@@ -8,6 +8,8 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
service_overview "github.com/APIParkLab/APIPark/service/service-overview"
|
||||
|
||||
ai_provider_local "github.com/APIParkLab/APIPark/ai-provider/local"
|
||||
|
||||
model_runtime "github.com/APIParkLab/APIPark/ai-provider/model-runtime"
|
||||
@@ -35,12 +37,13 @@ var (
|
||||
)
|
||||
|
||||
type imlAPIModule struct {
|
||||
serviceService service.IServiceService `autowired:""`
|
||||
apiDocService api_doc.IAPIDocService `autowired:""`
|
||||
aiAPIService ai_api.IAPIService `autowired:""`
|
||||
aiModelService ai_model.IProviderModelService `autowired:""`
|
||||
apiService api.IAPIService `autowired:""`
|
||||
transaction store.ITransaction `autowired:""`
|
||||
serviceService service.IServiceService `autowired:""`
|
||||
serviceOverviewService service_overview.IOverviewService `autowired:""`
|
||||
apiDocService api_doc.IAPIDocService `autowired:""`
|
||||
aiAPIService ai_api.IAPIService `autowired:""`
|
||||
aiModelService ai_model.IProviderModelService `autowired:""`
|
||||
apiService api.IAPIService `autowired:""`
|
||||
transaction store.ITransaction `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlAPIModule) getAPIDoc(ctx context.Context, serviceId string) (*openapi3.T, error) {
|
||||
@@ -77,9 +80,17 @@ func (i *imlAPIModule) updateAPIDoc(ctx context.Context, serviceId, serviceName,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return i.apiDocService.UpdateDoc(ctx, serviceId, &api_doc.UpdateDoc{
|
||||
ID: uuid.New().String(),
|
||||
Content: string(result),
|
||||
return i.transaction.Transaction(ctx, func(ctx context.Context) error {
|
||||
count, err := i.apiDocService.UpdateDoc(ctx, serviceId, &api_doc.UpdateDoc{
|
||||
ID: uuid.New().String(),
|
||||
Content: string(result),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("update api doc error:%v", err)
|
||||
}
|
||||
return i.serviceOverviewService.Update(ctx, serviceId, &service_overview.Update{
|
||||
ApiCount: &count,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -93,10 +104,19 @@ func (i *imlAPIModule) deleteAPIDoc(ctx context.Context, serviceId string, path
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return i.apiDocService.UpdateDoc(ctx, serviceId, &api_doc.UpdateDoc{
|
||||
ID: uuid.New().String(),
|
||||
Content: string(result),
|
||||
return i.transaction.Transaction(ctx, func(ctx context.Context) error {
|
||||
count, err := i.apiDocService.UpdateDoc(ctx, serviceId, &api_doc.UpdateDoc{
|
||||
ID: uuid.New().String(),
|
||||
Content: string(result),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("update api doc error:%v", err)
|
||||
}
|
||||
return i.serviceOverviewService.Update(ctx, serviceId, &service_overview.Update{
|
||||
ApiCount: &count,
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (i *imlAPIModule) Create(ctx context.Context, serviceId string, input *ai_api_dto.CreateAPI) error {
|
||||
|
||||
+24
-15
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
func genOpenAPI3Template(title string, description string) *openapi3.T {
|
||||
result := new(openapi3.T)
|
||||
result.OpenAPI = "3.1.0"
|
||||
result.OpenAPI = "3.0.1"
|
||||
result.Info = &openapi3.Info{
|
||||
Title: title,
|
||||
Description: description,
|
||||
@@ -37,6 +37,8 @@ func genOperation(summary string, description string, variables []*ai_api_dto.Ai
|
||||
|
||||
func genRequestBody(variables []*ai_api_dto.AiPromptVariable) *openapi3.RequestBodyRef {
|
||||
requestBody := openapi3.NewRequestBody()
|
||||
requestBody.Description = "Request body"
|
||||
requestBody.Required = true
|
||||
requestBody.Content = openapi3.NewContentWithSchema(genRequestBodySchema(variables), []string{"application/json"})
|
||||
return &openapi3.RequestBodyRef{
|
||||
Value: requestBody,
|
||||
@@ -55,10 +57,14 @@ func genResponse() *openapi3.ResponseRef {
|
||||
|
||||
func genRequestBodySchema(variables []*ai_api_dto.AiPromptVariable) *openapi3.Schema {
|
||||
result := openapi3.NewObjectSchema()
|
||||
required := make([]string, 0, 2)
|
||||
required = append(required, "messages")
|
||||
if len(variables) > 0 {
|
||||
result.WithProperty("variables", genVariableSchema(variables))
|
||||
result.WithRequired([]string{"variables", "messages"})
|
||||
required = append(required, "variables")
|
||||
}
|
||||
|
||||
result.WithRequired(required)
|
||||
streamSchema := openapi3.NewBoolSchema()
|
||||
streamSchema.Title = "stream"
|
||||
streamSchema.Description = "Whether to stream the response"
|
||||
@@ -129,6 +135,8 @@ func genMessageSchema() *openapi3.Schema {
|
||||
"role": roleSchema,
|
||||
"content": contentSchema,
|
||||
})
|
||||
|
||||
result.WithRequired([]string{"role", "content"})
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -137,20 +145,21 @@ func genMessagesSchema() *openapi3.Schema {
|
||||
result.Title = "Messages"
|
||||
result.Description = "Chat Messages"
|
||||
result.Items = openapi3.NewSchemaRef("#/components/schemas/Message", messageSchema)
|
||||
result.Required = []string{"content", "role"}
|
||||
return result
|
||||
}
|
||||
|
||||
func genResponseSchema() *openapi3.Schema {
|
||||
result := openapi3.NewObjectSchema()
|
||||
result.Description = "Response from the server"
|
||||
|
||||
|
||||
// 创建 choices 数组
|
||||
choicesSchema := openapi3.NewArraySchema()
|
||||
choiceItemSchema := openapi3.NewObjectSchema()
|
||||
|
||||
|
||||
// choice 中的 message 字段
|
||||
choiceItemSchema.WithPropertyRef("message", messageSchemaRef)
|
||||
|
||||
|
||||
// finish_reason 字段
|
||||
finishReasonSchema := openapi3.NewStringSchema().WithEnum(
|
||||
"stop",
|
||||
@@ -160,41 +169,41 @@ func genResponseSchema() *openapi3.Schema {
|
||||
"null",
|
||||
)
|
||||
choiceItemSchema.WithProperty("finish_reason", finishReasonSchema)
|
||||
|
||||
|
||||
// index 字段
|
||||
choiceItemSchema.WithProperty("index", openapi3.NewIntegerSchema())
|
||||
|
||||
|
||||
// logprobs 字段,可以为 null
|
||||
choiceItemSchema.WithProperty("logprobs", openapi3.NewSchema().WithNullable())
|
||||
|
||||
|
||||
choicesSchema.Items = &openapi3.SchemaRef{Value: choiceItemSchema}
|
||||
result.WithProperty("choices", choicesSchema)
|
||||
|
||||
|
||||
// object 字段
|
||||
result.WithProperty("object", openapi3.NewStringSchema().WithEnum("chat.completion"))
|
||||
|
||||
|
||||
// usage 字段
|
||||
usageSchema := openapi3.NewObjectSchema()
|
||||
usageSchema.WithProperty("prompt_tokens", openapi3.NewIntegerSchema())
|
||||
usageSchema.WithProperty("completion_tokens", openapi3.NewIntegerSchema())
|
||||
usageSchema.WithProperty("total_tokens", openapi3.NewIntegerSchema())
|
||||
|
||||
|
||||
// prompt_tokens_details 字段
|
||||
promptTokensDetailsSchema := openapi3.NewObjectSchema()
|
||||
promptTokensDetailsSchema.WithProperty("cached_tokens", openapi3.NewIntegerSchema())
|
||||
usageSchema.WithProperty("prompt_tokens_details", promptTokensDetailsSchema)
|
||||
|
||||
|
||||
result.WithProperty("usage", usageSchema)
|
||||
|
||||
|
||||
// 其他字段
|
||||
result.WithProperty("created", openapi3.NewIntegerSchema())
|
||||
result.WithProperty("system_fingerprint", openapi3.NewStringSchema().WithNullable())
|
||||
result.WithProperty("model", openapi3.NewStringSchema())
|
||||
result.WithProperty("id", openapi3.NewStringSchema())
|
||||
|
||||
|
||||
// 保留原有的错误字段
|
||||
result.WithProperty("code", openapi3.NewIntegerSchema())
|
||||
result.WithProperty("error", openapi3.NewStringSchema())
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@ package api_doc
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/eolinker/go-common/store"
|
||||
|
||||
service_overview "github.com/APIParkLab/APIPark/service/service-overview"
|
||||
|
||||
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
|
||||
api_doc "github.com/APIParkLab/APIPark/service/api-doc"
|
||||
@@ -16,8 +21,10 @@ import (
|
||||
var _ IAPIDocModule = (*imlAPIDocModule)(nil)
|
||||
|
||||
type imlAPIDocModule struct {
|
||||
apiDocService api_doc.IAPIDocService `autowired:""`
|
||||
serviceService service.IServiceService `autowired:""`
|
||||
apiDocService api_doc.IAPIDocService `autowired:""`
|
||||
serviceService service.IServiceService `autowired:""`
|
||||
serviceOverviewService service_overview.IOverviewService `autowired:""`
|
||||
transaction store.ITransaction `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlAPIDocModule) UpdateDoc(ctx context.Context, serviceId string, input *api_doc_dto.UpdateDoc) (*api_doc_dto.ApiDocDetail, error) {
|
||||
@@ -29,11 +36,18 @@ func (i *imlAPIDocModule) UpdateDoc(ctx context.Context, serviceId string, input
|
||||
input.Id = uuid.New().String()
|
||||
}
|
||||
// 每个API加上前缀
|
||||
|
||||
err = i.apiDocService.UpdateDoc(ctx, serviceId, &api_doc.UpdateDoc{
|
||||
ID: input.Id,
|
||||
Content: input.Content,
|
||||
Prefix: info.Prefix,
|
||||
err = i.transaction.Transaction(ctx, func(ctx context.Context) error {
|
||||
count, err := i.apiDocService.UpdateDoc(ctx, serviceId, &api_doc.UpdateDoc{
|
||||
ID: input.Id,
|
||||
Content: input.Content,
|
||||
Prefix: info.Prefix,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("update api doc error:%v", err)
|
||||
}
|
||||
return i.serviceOverviewService.Update(ctx, serviceId, &service_overview.Update{
|
||||
ApiCount: &count,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+35
-32
@@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
service_overview "github.com/APIParkLab/APIPark/service/service-overview"
|
||||
|
||||
mcp_server "github.com/APIParkLab/APIPark/mcp-server"
|
||||
|
||||
"github.com/APIParkLab/APIPark/module/monitor/driver"
|
||||
@@ -58,21 +60,22 @@ var (
|
||||
)
|
||||
|
||||
type imlCatalogueModule struct {
|
||||
catalogueService catalogue.ICatalogueService `autowired:""`
|
||||
apiService api.IAPIService `autowired:""`
|
||||
apiDocService api_doc.IAPIDocService `autowired:""`
|
||||
serviceService service.IServiceService `autowired:""`
|
||||
serviceTagService service_tag.ITagService `autowired:""`
|
||||
serviceDocService service_doc.IDocService `autowired:""`
|
||||
tagService tag.ITagService `autowired:""`
|
||||
releaseService release.IReleaseService `autowired:""`
|
||||
subscribeService subscribe.ISubscribeService `autowired:""`
|
||||
subscribeApplyService subscribe.ISubscribeApplyService `autowired:""`
|
||||
transaction store.ITransaction `autowired:""`
|
||||
clusterService cluster.IClusterService `autowired:""`
|
||||
settingService setting.ISettingService `autowired:""`
|
||||
monitorService monitor.IMonitorService `autowired:""`
|
||||
root *Root
|
||||
catalogueService catalogue.ICatalogueService `autowired:""`
|
||||
apiService api.IAPIService `autowired:""`
|
||||
apiDocService api_doc.IAPIDocService `autowired:""`
|
||||
serviceService service.IServiceService `autowired:""`
|
||||
serviceOverviewService service_overview.IOverviewService `autowired:""`
|
||||
serviceTagService service_tag.ITagService `autowired:""`
|
||||
serviceDocService service_doc.IDocService `autowired:""`
|
||||
tagService tag.ITagService `autowired:""`
|
||||
releaseService release.IReleaseService `autowired:""`
|
||||
subscribeService subscribe.ISubscribeService `autowired:""`
|
||||
subscribeApplyService subscribe.ISubscribeApplyService `autowired:""`
|
||||
transaction store.ITransaction `autowired:""`
|
||||
clusterService cluster.IClusterService `autowired:""`
|
||||
settingService setting.ISettingService `autowired:""`
|
||||
monitorService monitor.IMonitorService `autowired:""`
|
||||
root *Root
|
||||
}
|
||||
|
||||
func (i *imlCatalogueModule) DefaultCatalogue(ctx context.Context) (*catalogue_dto.Catalogue, error) {
|
||||
@@ -447,27 +450,26 @@ func (i *imlCatalogueModule) Services(ctx context.Context, keyword string) ([]*c
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serviceIds := utils.SliceToSlice(items, func(i *service.Service) string {
|
||||
return i.Id
|
||||
}, func(s *service.Service) bool {
|
||||
// 未发布的不给展示
|
||||
_, err = i.releaseService.GetRunning(ctx, s.Id)
|
||||
return err == nil
|
||||
})
|
||||
overviewMap, err := i.serviceOverviewService.Map(ctx, serviceIds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serviceIds = utils.SliceToSlice(serviceIds, func(s string) string {
|
||||
return s
|
||||
}, func(s string) bool {
|
||||
// 只展示已发布的服务
|
||||
if info, ok := overviewMap[s]; ok && info.IsReleased {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if len(serviceIds) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
commits, err := i.releaseService.GetRunningApiDocCommits(ctx, serviceIds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiCountMap, err := i.apiDocService.LatestAPICountByCommits(ctx, commits...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subscriberCountMap, err := i.subscribeService.CountMapByService(ctx, subscribe.ApplyStatusSubscribe, serviceIds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -479,8 +481,9 @@ func (i *imlCatalogueModule) Services(ctx context.Context, keyword string) ([]*c
|
||||
|
||||
result := make([]*catalogue_dto.ServiceItem, 0, len(items))
|
||||
for _, v := range items {
|
||||
apiNum, ok := apiCountMap[v.Id]
|
||||
if !ok || apiNum < 1 {
|
||||
|
||||
ov, ok := overviewMap[v.Id]
|
||||
if !ok || ov.ReleaseApiCount < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -489,8 +492,8 @@ func (i *imlCatalogueModule) Services(ctx context.Context, keyword string) ([]*c
|
||||
Name: v.Name,
|
||||
Tags: auto.List(serviceTagMap[v.Id]),
|
||||
Catalogue: auto.UUID(v.Catalogue),
|
||||
ApiNum: apiNum,
|
||||
SubscriberNum: subscriberCountMap[v.Id],
|
||||
ApiNum: ov.ReleaseApiCount,
|
||||
Description: v.Description,
|
||||
Logo: v.Logo,
|
||||
EnableMCP: v.EnableMCP,
|
||||
|
||||
+72
-73
@@ -10,7 +10,6 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/APIParkLab/APIPark/service/subscribe"
|
||||
|
||||
@@ -48,7 +47,7 @@ type imlMcpModule struct {
|
||||
}
|
||||
|
||||
func (i *imlMcpModule) Services(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
keyword, _ := req.Params.Arguments["keyword"].(string)
|
||||
keyword, _ := req.GetArguments()["keyword"].(string)
|
||||
list, err := i.serviceService.Search(ctx, keyword, map[string]interface{}{
|
||||
"as_server": true,
|
||||
}, "update_at desc")
|
||||
@@ -116,34 +115,34 @@ func (i *imlMcpModule) Services(ctx context.Context, req mcp.CallToolRequest) (*
|
||||
|
||||
}
|
||||
|
||||
func (i *imlMcpModule) Apps(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
keyword := req.Params.Arguments["keyword"].(string)
|
||||
condition := make(map[string]interface{})
|
||||
condition["as_app"] = true
|
||||
list, err := i.serviceService.Search(ctx, keyword, condition, "update_at desc")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search service error: %w", err)
|
||||
}
|
||||
if len(list) == 0 {
|
||||
list, err = i.serviceService.Search(ctx, "", condition, "update_at desc")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search service error: %w", err)
|
||||
}
|
||||
}
|
||||
data, _ := json.Marshal(utils.SliceToSlice(list, func(s *service.Service) *mcp_dto.App {
|
||||
return &mcp_dto.App{
|
||||
Id: s.Id,
|
||||
Name: s.Name,
|
||||
Description: s.Name,
|
||||
CreateTime: s.CreateTime,
|
||||
UpdateTime: s.UpdateTime,
|
||||
}
|
||||
}))
|
||||
return mcp.NewToolResultText(string(data)), nil
|
||||
}
|
||||
//func (i *imlMcpModule) Apps(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// keyword := req.GetArguments()["keyword"].(string)
|
||||
// condition := make(map[string]interface{})
|
||||
// condition["as_app"] = true
|
||||
// list, err := i.serviceService.Search(ctx, keyword, condition, "update_at desc")
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("search service error: %w", err)
|
||||
// }
|
||||
// if len(list) == 0 {
|
||||
// list, err = i.serviceService.Search(ctx, "", condition, "update_at desc")
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("search service error: %w", err)
|
||||
// }
|
||||
// }
|
||||
// data, _ := json.Marshal(utils.SliceToSlice(list, func(s *service.Service) *mcp_dto.App {
|
||||
// return &mcp_dto.App{
|
||||
// Id: s.Id,
|
||||
// Name: s.Name,
|
||||
// Description: s.Name,
|
||||
// CreateTime: s.CreateTime,
|
||||
// UpdateTime: s.UpdateTime,
|
||||
// }
|
||||
// }))
|
||||
// return mcp.NewToolResultText(string(data)), nil
|
||||
//}
|
||||
|
||||
func (i *imlMcpModule) APIs(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
serviceId, _ := req.Params.Arguments["service"].(string)
|
||||
serviceId, _ := req.GetArguments()["service"].(string)
|
||||
serviceIds := make([]string, 0, 1)
|
||||
if serviceId == "" {
|
||||
serviceIds = append(serviceIds, serviceId)
|
||||
@@ -190,45 +189,45 @@ func (i *imlMcpModule) APIs(ctx context.Context, req mcp.CallToolRequest) (*mcp.
|
||||
return mcp.NewToolResultText(string(data)), nil
|
||||
}
|
||||
|
||||
func (i *imlMcpModule) SubscriberAuthorizations(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
serviceId, ok := req.Params.Arguments["service"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("service id is required")
|
||||
}
|
||||
subscribes, err := i.subscriberService.Subscribers(ctx, serviceId, subscribe.ApplyStatusSubscribe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get subscriber error: %w,service id is %s", err, serviceId)
|
||||
}
|
||||
appIds := utils.SliceToSlice(subscribes, func(s *subscribe.Subscribe) string {
|
||||
return s.Application
|
||||
})
|
||||
if len(appIds) == 0 {
|
||||
return nil, fmt.Errorf("no subscriber found,service id is %s", serviceId)
|
||||
}
|
||||
list, err := i.appAuthorizationService.ListByApp(ctx, appIds...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get app authorization error: %w,app ids is %s", err, appIds)
|
||||
}
|
||||
result := utils.SliceToSlice(list, func(a *application_authorization.Authorization) *mcp_dto.AppAuthorization {
|
||||
return &mcp_dto.AppAuthorization{
|
||||
Id: a.UUID,
|
||||
Name: a.Name,
|
||||
Position: a.Position,
|
||||
TokenName: a.TokenName,
|
||||
Config: a.Config,
|
||||
}
|
||||
}, func(a *application_authorization.Authorization) bool {
|
||||
if a.Type != "apikey" {
|
||||
return false
|
||||
}
|
||||
if a.ExpireTime != 0 && a.ExpireTime < time.Now().Unix() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
data, _ := json.Marshal(result)
|
||||
return mcp.NewToolResultText(string(data)), nil
|
||||
}
|
||||
//func (i *imlMcpModule) SubscriberAuthorizations(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// serviceId, ok := req.GetArguments()["service"].(string)
|
||||
// if !ok {
|
||||
// return nil, fmt.Errorf("service id is required")
|
||||
// }
|
||||
// subscribes, err := i.subscriberService.Subscribers(ctx, serviceId, subscribe.ApplyStatusSubscribe)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("get subscriber error: %w,service id is %s", err, serviceId)
|
||||
// }
|
||||
// appIds := utils.SliceToSlice(subscribes, func(s *subscribe.Subscribe) string {
|
||||
// return s.Application
|
||||
// })
|
||||
// if len(appIds) == 0 {
|
||||
// return nil, fmt.Errorf("no subscriber found,service id is %s", serviceId)
|
||||
// }
|
||||
// list, err := i.appAuthorizationService.ListByApp(ctx, appIds...)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("get app authorization error: %w,app ids is %s", err, appIds)
|
||||
// }
|
||||
// result := utils.SliceToSlice(list, func(a *application_authorization.Authorization) *mcp_dto.AppAuthorization {
|
||||
// return &mcp_dto.AppAuthorization{
|
||||
// Id: a.UUID,
|
||||
// Name: a.Name,
|
||||
// position: a.position,
|
||||
// TokenName: a.TokenName,
|
||||
// Config: a.Config,
|
||||
// }
|
||||
// }, func(a *application_authorization.Authorization) bool {
|
||||
// if a.Type != "apikey" {
|
||||
// return false
|
||||
// }
|
||||
// if a.ExpireTime != 0 && a.ExpireTime < time.Now().Unix() {
|
||||
// return false
|
||||
// }
|
||||
// return true
|
||||
// })
|
||||
// data, _ := json.Marshal(result)
|
||||
// return mcp.NewToolResultText(string(data)), nil
|
||||
//}
|
||||
|
||||
var (
|
||||
client = &http.Client{}
|
||||
@@ -248,18 +247,18 @@ func (i *imlMcpModule) Invoke(ctx context.Context, req mcp.CallToolRequest) (*mc
|
||||
u.Scheme = "http"
|
||||
}
|
||||
|
||||
path, ok := req.Params.Arguments["path"].(string)
|
||||
path, ok := req.GetArguments()["path"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid path")
|
||||
}
|
||||
u.Path = fmt.Sprintf("%s/%s", strings.TrimSuffix(u.Path, "/"), strings.TrimPrefix(path, "/"))
|
||||
|
||||
method, ok := req.Params.Arguments["method"].(string)
|
||||
method, ok := req.GetArguments()["method"].(string)
|
||||
if !ok {
|
||||
method = "GET"
|
||||
}
|
||||
queryParam := url.Values{}
|
||||
query, ok := req.Params.Arguments["query"].(map[string]interface{})
|
||||
query, ok := req.GetArguments()["query"].(map[string]interface{})
|
||||
if ok {
|
||||
for k, v := range query {
|
||||
switch v := v.(type) {
|
||||
@@ -278,7 +277,7 @@ func (i *imlMcpModule) Invoke(ctx context.Context, req mcp.CallToolRequest) (*mc
|
||||
}
|
||||
u.RawQuery = queryParam.Encode()
|
||||
headerParam := http.Header{}
|
||||
header, ok := req.Params.Arguments["header"].(map[string]interface{})
|
||||
header, ok := req.GetArguments()["header"].(map[string]interface{})
|
||||
if ok {
|
||||
for k, v := range header {
|
||||
switch v := v.(type) {
|
||||
@@ -294,12 +293,12 @@ func (i *imlMcpModule) Invoke(ctx context.Context, req mcp.CallToolRequest) (*mc
|
||||
}
|
||||
}
|
||||
|
||||
body, ok := req.Params.Arguments["body"].(string)
|
||||
body, ok := req.GetArguments()["body"].(string)
|
||||
if !ok {
|
||||
body = ""
|
||||
}
|
||||
|
||||
contentType, ok := req.Params.Arguments["content-type"].(string)
|
||||
contentType, ok := req.GetArguments()["content-type"].(string)
|
||||
if !ok {
|
||||
contentType = "application/json"
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ type IMcpModule interface {
|
||||
// Services 获取服务列表
|
||||
Services(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
// Apps 获取应用列表
|
||||
Apps(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
//Apps(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
// APIs 获取API列表
|
||||
APIs(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
|
||||
// SubscriberAuthorizations 获取订阅者授权
|
||||
SubscriberAuthorizations(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
//SubscriberAuthorizations(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
Invoke(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
}
|
||||
|
||||
|
||||
+150
-151
@@ -7,10 +7,14 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/eolinker/go-common/server"
|
||||
|
||||
"github.com/eolinker/go-common/register"
|
||||
|
||||
service_overview "github.com/APIParkLab/APIPark/service/service-overview"
|
||||
|
||||
mcp_server "github.com/APIParkLab/APIPark/mcp-server"
|
||||
api_doc "github.com/APIParkLab/APIPark/service/api-doc"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
strategy_driver "github.com/APIParkLab/APIPark/module/strategy/driver"
|
||||
strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"
|
||||
@@ -51,22 +55,70 @@ var (
|
||||
)
|
||||
|
||||
type imlPublishModule struct {
|
||||
projectDiffModule serviceDiff.IServiceDiffModule `autowired:""`
|
||||
releaseModule releaseModule.IReleaseModule `autowired:""`
|
||||
publishService publish.IPublishService `autowired:""`
|
||||
apiService api.IAPIService `autowired:""`
|
||||
apiDocService api_doc.IAPIDocService `autowired:""`
|
||||
upstreamService upstream.IUpstreamService `autowired:""`
|
||||
strategyService strategy.IStrategyService `autowired:""`
|
||||
releaseService release.IReleaseService `autowired:""`
|
||||
clusterService cluster.IClusterService `autowired:""`
|
||||
serviceService service.IServiceService `autowired:""`
|
||||
transaction store.ITransaction `autowired:""`
|
||||
projectDiffModule serviceDiff.IServiceDiffModule `autowired:""`
|
||||
releaseModule releaseModule.IReleaseModule `autowired:""`
|
||||
publishService publish.IPublishService `autowired:""`
|
||||
apiService api.IAPIService `autowired:""`
|
||||
apiDocService api_doc.IAPIDocService `autowired:""`
|
||||
upstreamService upstream.IUpstreamService `autowired:""`
|
||||
strategyService strategy.IStrategyService `autowired:""`
|
||||
releaseService release.IReleaseService `autowired:""`
|
||||
clusterService cluster.IClusterService `autowired:""`
|
||||
serviceService service.IServiceService `autowired:""`
|
||||
serviceOverviewService service_overview.IOverviewService `autowired:""`
|
||||
transaction store.ITransaction `autowired:""`
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) initGateway(ctx context.Context, partitionId string, clientDriver gateway.IClientDriver) error {
|
||||
func (i *imlPublishModule) OnInit() {
|
||||
register.Handle(func(v server.Server) {
|
||||
ctx := context.Background()
|
||||
list, err := i.releaseService.GetRunningList(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("onInit: get running list failed:%s", err.Error())
|
||||
return
|
||||
}
|
||||
if len(list) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
serviceMap := make(map[string]*release.Release)
|
||||
serviceIds := make([]string, 0, len(list))
|
||||
for _, v := range list {
|
||||
if _, ok := serviceMap[v.Service]; !ok {
|
||||
serviceMap[v.Service] = v
|
||||
serviceIds = append(serviceIds, v.Service)
|
||||
}
|
||||
}
|
||||
overviewList, err := i.serviceOverviewService.List(ctx, serviceIds...)
|
||||
if err != nil {
|
||||
log.Errorf("onInit: get running list failed:%s", err.Error())
|
||||
return
|
||||
}
|
||||
for _, v := range overviewList {
|
||||
if v.IsReleased {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
listCommits, err := i.apiDocService.ListLatestDocCommit(ctx, serviceIds...)
|
||||
if err != nil {
|
||||
log.Errorf("onInit: get running api doc commits failed:%s", err.Error())
|
||||
return
|
||||
}
|
||||
isReleased := true
|
||||
for _, v := range listCommits {
|
||||
i.serviceOverviewService.Update(ctx, v.Target, &service_overview.Update{
|
||||
ApiCount: nil,
|
||||
ReleaseApiCount: &v.Data.APICount,
|
||||
IsReleased: &isReleased,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (i *imlPublishModule) initGateway(ctx context.Context, partitionId string, clientDriver gateway.IClientDriver) error {
|
||||
return nil
|
||||
//projects, err := m.serviceService.List(ctx)
|
||||
//projects, err := i.serviceService.List(ctx)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
@@ -74,7 +126,7 @@ func (m *imlPublishModule) initGateway(ctx context.Context, partitionId string,
|
||||
// return p.Id
|
||||
//})
|
||||
//for _, projectId := range projectIds {
|
||||
// releaseInfo, err := m.GetProjectRelease(ctx, projectId, partitionId)
|
||||
// releaseInfo, err := i.GetProjectRelease(ctx, projectId, partitionId)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
@@ -90,8 +142,8 @@ func (m *imlPublishModule) initGateway(ctx context.Context, partitionId string,
|
||||
//return nil
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID string, commitId string) (*gateway.ProjectRelease, error) {
|
||||
commits, err := m.releaseService.GetCommits(ctx, commitId)
|
||||
func (i *imlPublishModule) getProjectRelease(ctx context.Context, projectID string, commitId string) (*gateway.ProjectRelease, error) {
|
||||
commits, err := i.releaseService.GetCommits(ctx, commitId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -110,17 +162,17 @@ func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID stri
|
||||
strategyCommitIds = append(strategyCommitIds, c.Commit)
|
||||
}
|
||||
}
|
||||
serviceInfo, err := m.serviceService.Get(ctx, projectID)
|
||||
serviceInfo, err := i.serviceService.Get(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiInfos, err := m.apiService.ListInfo(ctx, apiIds...)
|
||||
apiInfos, err := i.apiService.ListInfo(ctx, apiIds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyCommits, err := m.apiService.ListProxyCommit(ctx, apiProxyCommitIds...)
|
||||
proxyCommits, err := i.apiService.ListProxyCommit(ctx, apiProxyCommitIds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -177,7 +229,7 @@ func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID stri
|
||||
r.Apis = apis
|
||||
var upstreamRelease *gateway.UpstreamRelease
|
||||
if len(upstreamCommitIds) > 0 {
|
||||
upstreamCommits, err := m.upstreamService.ListCommit(ctx, upstreamCommitIds...)
|
||||
upstreamCommits, err := i.upstreamService.ListCommit(ctx, upstreamCommitIds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -202,7 +254,7 @@ func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID stri
|
||||
r.Upstream = upstreamRelease
|
||||
}
|
||||
if len(strategyCommitIds) > 0 {
|
||||
strategyCommits, err := m.strategyService.ListStrategyCommit(ctx, strategyCommitIds...)
|
||||
strategyCommits, err := i.strategyService.ListStrategyCommit(ctx, strategyCommitIds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -234,9 +286,9 @@ func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID stri
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) GetProjectRelease(ctx context.Context, projectID string, partitionId string) (*gateway.ProjectRelease, error) {
|
||||
func (i *imlPublishModule) GetProjectRelease(ctx context.Context, projectID string, partitionId string) (*gateway.ProjectRelease, error) {
|
||||
|
||||
releaseInfo, err := m.releaseService.GetRunning(ctx, projectID)
|
||||
releaseInfo, err := i.releaseService.GetRunning(ctx, projectID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
@@ -244,11 +296,11 @@ func (m *imlPublishModule) GetProjectRelease(ctx context.Context, projectID stri
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return m.getProjectRelease(ctx, projectID, releaseInfo.UUID)
|
||||
return i.getProjectRelease(ctx, projectID, releaseInfo.UUID)
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) getReleaseInfo(ctx context.Context, projectID, releaseId, version string, clusterIds []string) (map[string]*gateway.ProjectRelease, error) {
|
||||
projectRelease, err := m.getProjectRelease(ctx, projectID, releaseId)
|
||||
func (i *imlPublishModule) getReleaseInfo(ctx context.Context, projectID, releaseId, version string, clusterIds []string) (map[string]*gateway.ProjectRelease, error) {
|
||||
projectRelease, err := i.getProjectRelease(ctx, projectID, releaseId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -266,19 +318,19 @@ func (m *imlPublishModule) getReleaseInfo(ctx context.Context, projectID, releas
|
||||
return projectReleaseMap, nil
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) PublishStatuses(ctx context.Context, serviceId string, id string) ([]*dto.PublishStatus, error) {
|
||||
_, err := m.serviceService.Check(ctx, serviceId, asServer)
|
||||
func (i *imlPublishModule) PublishStatuses(ctx context.Context, serviceId string, id string) ([]*dto.PublishStatus, error) {
|
||||
_, err := i.serviceService.Check(ctx, serviceId, asServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
flow, err := m.publishService.Get(ctx, id)
|
||||
flow, err := i.publishService.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if flow.Service != serviceId {
|
||||
return nil, errors.New("服务不一致")
|
||||
}
|
||||
list, err := m.publishService.GetPublishStatus(ctx, id)
|
||||
list, err := i.publishService.GetPublishStatus(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -302,18 +354,18 @@ func (m *imlPublishModule) PublishStatuses(ctx context.Context, serviceId string
|
||||
//
|
||||
// ctx context.Context, serviceId string, input *dto.ApplyInput
|
||||
// *dto.Publish, error
|
||||
func (m *imlPublishModule) Apply(ctx context.Context, serviceId string, input *dto.ApplyInput) (*dto.Publish, error) {
|
||||
_, err := m.serviceService.Check(ctx, serviceId, asServer)
|
||||
func (i *imlPublishModule) Apply(ctx context.Context, serviceId string, input *dto.ApplyInput) (*dto.Publish, error) {
|
||||
_, err := i.serviceService.Check(ctx, serviceId, asServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = m.checkPublish(ctx, serviceId, input.Release)
|
||||
err = i.checkPublish(ctx, serviceId, input.Release)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
previous := ""
|
||||
running, err := m.releaseService.GetRunning(ctx, serviceId)
|
||||
running, err := i.releaseService.GetRunning(ctx, serviceId)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
||||
return nil, err
|
||||
@@ -322,42 +374,42 @@ func (m *imlPublishModule) Apply(ctx context.Context, serviceId string, input *d
|
||||
previous = running.UUID
|
||||
}
|
||||
|
||||
releaseToPublish, err := m.releaseService.GetRelease(ctx, input.Release)
|
||||
releaseToPublish, err := i.releaseService.GetRelease(ctx, input.Release)
|
||||
if err != nil {
|
||||
// 目标版本不存在
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newPublishId := uuid.NewString()
|
||||
diff, ok, err := m.projectDiffModule.DiffForLatest(ctx, serviceId, previous)
|
||||
diff, ok, err := i.projectDiffModule.DiffForLatest(ctx, serviceId, previous)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, errors.New("latest completeness check failed")
|
||||
}
|
||||
err = m.publishService.Create(ctx, newPublishId, serviceId, releaseToPublish.UUID, previous, releaseToPublish.Version, input.Remark, diff)
|
||||
err = i.publishService.Create(ctx, newPublishId, serviceId, releaseToPublish.UUID, previous, releaseToPublish.Version, input.Remark, diff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
np, err := m.publishService.Get(ctx, newPublishId)
|
||||
np, err := i.publishService.Get(ctx, newPublishId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dto.FromModel(np, releaseToPublish.Remark), nil
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) CheckPublish(ctx context.Context, serviceId string, releaseId string) (*dto.DiffOut, error) {
|
||||
_, err := m.serviceService.Check(ctx, serviceId, asServer)
|
||||
func (i *imlPublishModule) CheckPublish(ctx context.Context, serviceId string, releaseId string) (*dto.DiffOut, error) {
|
||||
_, err := i.serviceService.Check(ctx, serviceId, asServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = m.checkPublish(ctx, serviceId, releaseId)
|
||||
err = i.checkPublish(ctx, serviceId, releaseId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
running, err := m.releaseService.GetRunning(ctx, serviceId)
|
||||
running, err := i.releaseService.GetRunning(ctx, serviceId)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
@@ -367,30 +419,30 @@ func (m *imlPublishModule) CheckPublish(ctx context.Context, serviceId string, r
|
||||
}
|
||||
if releaseId == "" {
|
||||
// 发布latest 版本
|
||||
diff, _, err := m.projectDiffModule.DiffForLatest(ctx, serviceId, runningReleaseId)
|
||||
diff, _, err := i.projectDiffModule.DiffForLatest(ctx, serviceId, runningReleaseId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.projectDiffModule.Out(ctx, diff)
|
||||
return i.projectDiffModule.Out(ctx, diff)
|
||||
} else {
|
||||
// 发布 releaseId 版本, 返回 与当前版本的差异
|
||||
diff, err := m.projectDiffModule.Diff(ctx, serviceId, runningReleaseId, releaseId)
|
||||
diff, err := i.projectDiffModule.Diff(ctx, serviceId, runningReleaseId, releaseId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.projectDiffModule.Out(ctx, diff)
|
||||
return i.projectDiffModule.Out(ctx, diff)
|
||||
}
|
||||
|
||||
}
|
||||
func (m *imlPublishModule) checkPublish(ctx context.Context, serviceId string, releaseId string) error {
|
||||
flows, err := m.publishService.ListForStatus(ctx, serviceId, publish.StatusApply, publish.StatusAccept)
|
||||
func (i *imlPublishModule) checkPublish(ctx context.Context, serviceId string, releaseId string) error {
|
||||
flows, err := i.publishService.ListForStatus(ctx, serviceId, publish.StatusApply, publish.StatusAccept)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
if len(flows) > 0 {
|
||||
return errors.New("正在发布中")
|
||||
}
|
||||
running, err := m.releaseService.GetRunning(ctx, serviceId)
|
||||
running, err := i.releaseService.GetRunning(ctx, serviceId)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
@@ -403,8 +455,8 @@ func (m *imlPublishModule) checkPublish(ctx context.Context, serviceId string, r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *imlPublishModule) Close(ctx context.Context, serviceId, id string) error {
|
||||
err := m.publishService.SetStatus(ctx, serviceId, id, publish.StatusClose)
|
||||
func (i *imlPublishModule) Close(ctx context.Context, serviceId, id string) error {
|
||||
err := i.publishService.SetStatus(ctx, serviceId, id, publish.StatusClose)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -412,12 +464,12 @@ func (m *imlPublishModule) Close(ctx context.Context, serviceId, id string) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) Stop(ctx context.Context, serviceId string, id string) error {
|
||||
_, err := m.serviceService.Check(ctx, serviceId, asServer)
|
||||
func (i *imlPublishModule) Stop(ctx context.Context, serviceId string, id string) error {
|
||||
_, err := i.serviceService.Check(ctx, serviceId, asServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flow, err := m.publishService.Get(ctx, id)
|
||||
flow, err := i.publishService.Get(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -432,44 +484,44 @@ func (m *imlPublishModule) Stop(ctx context.Context, serviceId string, id string
|
||||
if flow.Status == publish.StatusApply {
|
||||
status = publish.StatusClose
|
||||
}
|
||||
return m.publishService.SetStatus(ctx, serviceId, id, status)
|
||||
return i.publishService.SetStatus(ctx, serviceId, id, status)
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) Refuse(ctx context.Context, serviceId string, id string, commits string) error {
|
||||
_, err := m.serviceService.Check(ctx, serviceId, asServer)
|
||||
func (i *imlPublishModule) Refuse(ctx context.Context, serviceId string, id string, commits string) error {
|
||||
_, err := i.serviceService.Check(ctx, serviceId, asServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.publishService.Refuse(ctx, serviceId, id, commits)
|
||||
return i.publishService.Refuse(ctx, serviceId, id, commits)
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) Accept(ctx context.Context, serviceId string, id string, commits string) error {
|
||||
_, err := m.serviceService.Check(ctx, serviceId, asServer)
|
||||
func (i *imlPublishModule) Accept(ctx context.Context, serviceId string, id string, commits string) error {
|
||||
_, err := i.serviceService.Check(ctx, serviceId, asServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.publishService.Accept(ctx, serviceId, id, commits)
|
||||
return i.publishService.Accept(ctx, serviceId, id, commits)
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) publish(ctx context.Context, id string, clusterId string, projectRelease *gateway.ProjectRelease) error {
|
||||
func (i *imlPublishModule) publish(ctx context.Context, id string, clusterId string, projectRelease *gateway.ProjectRelease) error {
|
||||
|
||||
publishStatus := &publish.Status{
|
||||
Publish: id,
|
||||
Status: publish.StatusPublishing,
|
||||
UpdateAt: time.Now(),
|
||||
}
|
||||
err := m.publishService.SetPublishStatus(ctx, publishStatus)
|
||||
err := i.publishService.SetPublishStatus(ctx, publishStatus)
|
||||
if err != nil {
|
||||
return fmt.Errorf("set publishing publishStatus error: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
err := m.publishService.SetPublishStatus(ctx, publishStatus)
|
||||
err := i.publishService.SetPublishStatus(ctx, publishStatus)
|
||||
if err != nil {
|
||||
log.Errorf("set publishing publishStatus error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
client, err := m.clusterService.GatewayClient(ctx, clusterId)
|
||||
client, err := i.clusterService.GatewayClient(ctx, clusterId)
|
||||
if err != nil {
|
||||
publishStatus.Status = publish.StatusPublishError
|
||||
publishStatus.Error = err.Error()
|
||||
@@ -501,12 +553,12 @@ func (m *imlPublishModule) publish(ctx context.Context, id string, clusterId str
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) Publish(ctx context.Context, serviceId string, id string) error {
|
||||
_, err := m.serviceService.Check(ctx, serviceId, asServer)
|
||||
func (i *imlPublishModule) Publish(ctx context.Context, serviceId string, id string) error {
|
||||
_, err := i.serviceService.Check(ctx, serviceId, asServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flow, err := m.publishService.Get(ctx, id)
|
||||
flow, err := i.publishService.Get(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -516,7 +568,7 @@ func (m *imlPublishModule) Publish(ctx context.Context, serviceId string, id str
|
||||
if flow.Status != publish.StatusAccept && flow.Status != publish.StatusDone {
|
||||
return errors.New("只有通过状态才能发布")
|
||||
}
|
||||
clusters, err := m.clusterService.List(ctx)
|
||||
clusters, err := i.clusterService.List(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -524,21 +576,21 @@ func (m *imlPublishModule) Publish(ctx context.Context, serviceId string, id str
|
||||
return i.Uuid
|
||||
})
|
||||
|
||||
projectReleaseMap, err := m.getReleaseInfo(ctx, serviceId, flow.Release, flow.Release, clusterIds)
|
||||
projectReleaseMap, err := i.getReleaseInfo(ctx, serviceId, flow.Release, flow.Release, clusterIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hasError := false
|
||||
return m.transaction.Transaction(ctx, func(ctx context.Context) error {
|
||||
return i.transaction.Transaction(ctx, func(ctx context.Context) error {
|
||||
for _, c := range clusters {
|
||||
err = m.publish(ctx, flow.Id, c.Uuid, projectReleaseMap[c.Uuid])
|
||||
err = i.publish(ctx, flow.Id, c.Uuid, projectReleaseMap[c.Uuid])
|
||||
if err != nil {
|
||||
hasError = true
|
||||
log.Error(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
err = m.releaseService.SetRunning(ctx, serviceId, flow.Release)
|
||||
err = i.releaseService.SetRunning(ctx, serviceId, flow.Release)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -547,29 +599,37 @@ func (m *imlPublishModule) Publish(ctx context.Context, serviceId string, id str
|
||||
status = publish.StatusPublishError
|
||||
}
|
||||
if status == publish.StatusDone {
|
||||
info, err := m.serviceService.Get(ctx, serviceId)
|
||||
info, err := i.serviceService.Get(ctx, serviceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.EnableMCP {
|
||||
err = m.updateMCPServer(ctx, serviceId, info.Name, flow.Version)
|
||||
err = i.updateMCPServer(ctx, serviceId, info.Name, flow.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
apidocCommit, err := i.apiDocService.LatestDocCommit(ctx, serviceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isReleased := true
|
||||
i.serviceOverviewService.Update(ctx, serviceId, &service_overview.Update{
|
||||
ReleaseApiCount: &apidocCommit.Data.APICount,
|
||||
IsReleased: &isReleased,
|
||||
})
|
||||
}
|
||||
|
||||
return m.publishService.SetStatus(ctx, serviceId, id, status)
|
||||
return i.publishService.SetStatus(ctx, serviceId, id, status)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) List(ctx context.Context, serviceId string, page, pageSize int) ([]*dto.Publish, int64, error) {
|
||||
_, err := m.serviceService.Check(ctx, serviceId, asServer)
|
||||
func (i *imlPublishModule) List(ctx context.Context, serviceId string, page, pageSize int) ([]*dto.Publish, int64, error) {
|
||||
_, err := i.serviceService.Check(ctx, serviceId, asServer)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
list, total, err := m.publishService.ListProjectPage(ctx, serviceId, page, pageSize)
|
||||
list, total, err := i.publishService.ListProjectPage(ctx, serviceId, page, pageSize)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -595,95 +655,34 @@ func (i *imlPublishModule) updateMCPServer(ctx context.Context, sid string, name
|
||||
if err != nil {
|
||||
return fmt.Errorf("get api doc commit error: %w", err)
|
||||
}
|
||||
mcpInfo, err := mcp_server.ConvertMCPFromOpenAPI3Data([]byte(commitDoc.Data.Content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert mcp from openapi3 data error: %w", err)
|
||||
}
|
||||
tools := make([]mcp_server.ITool, 0, len(mcpInfo.Apis))
|
||||
for _, a := range mcpInfo.Apis {
|
||||
toolOptions := make([]mcp.ToolOption, 0, len(a.Params)+2)
|
||||
toolOptions = append(toolOptions, mcp.WithDescription(a.Description))
|
||||
headers := make(map[string]interface{})
|
||||
queries := make(map[string]interface{})
|
||||
path := make(map[string]interface{})
|
||||
for _, v := range a.Params {
|
||||
p := map[string]interface{}{
|
||||
"type": "string",
|
||||
"required": v.Required,
|
||||
"description": v.Description,
|
||||
}
|
||||
switch v.In {
|
||||
case "header":
|
||||
headers[v.Name] = p
|
||||
case "query":
|
||||
queries[v.Name] = p
|
||||
case "path":
|
||||
path[v.Name] = p
|
||||
}
|
||||
}
|
||||
if len(headers) > 0 {
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPHeader, mcp.Properties(headers), mcp.Description("request headers.")))
|
||||
}
|
||||
if len(queries) > 0 {
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPQuery, mcp.Properties(queries), mcp.Description("request queries.")))
|
||||
}
|
||||
if len(path) > 0 {
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPPath, mcp.Properties(path), mcp.Description("request path params.")))
|
||||
}
|
||||
if a.Body != nil {
|
||||
type Schema struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Properties map[string]interface{} `mapstructure:"properties"`
|
||||
Items interface{} `mapstructure:"items"`
|
||||
}
|
||||
var tmp Schema
|
||||
err = mapstructure.Decode(a.Body, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//switch a.ContentType {
|
||||
//case "application/json":
|
||||
switch tmp.Type {
|
||||
case "object":
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPBody, mcp.Properties(tmp.Properties), mcp.Description("request body,it is avalible when method is POST、PUT、PATCH.")))
|
||||
case "array":
|
||||
toolOptions = append(toolOptions, mcp.WithArray(mcp_server.MCPBody, mcp.Items(tmp.Items), mcp.Description("request body,it is avalible when method is POST、PUT、PATCH.")))
|
||||
}
|
||||
//case "application/x-www-form-urlencoded":
|
||||
// toolOptions = append(toolOptions, mcp.WithString(mcp_server.MCPBody, mcp.Items(tmp.Items), mcp.Description("request body,it is avalible when method is POST、PUT、PATCH.")))
|
||||
|
||||
}
|
||||
tools = append(tools, mcp_server.NewTool(a.Summary, a.Path, a.Method, a.ContentType, toolOptions...))
|
||||
}
|
||||
mcp_server.SetSSEServer(sid, name, version, tools...)
|
||||
return nil
|
||||
return mcp_server.SetServerByOpenapi(sid, name, version, commitDoc.Data.Content)
|
||||
}
|
||||
|
||||
func (m *imlPublishModule) Detail(ctx context.Context, serviceId string, id string) (*dto.PublishDetail, error) {
|
||||
_, err := m.serviceService.Check(ctx, serviceId, asServer)
|
||||
func (i *imlPublishModule) Detail(ctx context.Context, serviceId string, id string) (*dto.PublishDetail, error) {
|
||||
_, err := i.serviceService.Check(ctx, serviceId, asServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
flow, err := m.publishService.Get(ctx, id)
|
||||
flow, err := i.publishService.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if flow.Service != serviceId {
|
||||
return nil, errors.New("项目不一致")
|
||||
}
|
||||
diff, err := m.publishService.GetDiff(ctx, id)
|
||||
diff, err := i.publishService.GetDiff(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := m.projectDiffModule.Out(ctx, diff)
|
||||
out, err := i.projectDiffModule.Out(ctx, diff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
publishStatuses, err := m.PublishStatuses(ctx, serviceId, id)
|
||||
publishStatuses, err := i.PublishStatuses(ctx, serviceId, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
releaseInfo, err := m.releaseService.GetRelease(ctx, flow.Release)
|
||||
releaseInfo, err := i.releaseService.GetRelease(ctx, flow.Release)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ func (m *imlReleaseModule) latestStrategyCommits(ctx context.Context, serviceId
|
||||
}
|
||||
|
||||
func (m *imlReleaseModule) Create(ctx context.Context, serviceId string, input *dto.CreateInput) (string, error) {
|
||||
|
||||
proInfo, err := m.projectService.Check(ctx, serviceId, projectRuleMustServer)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
||||
@@ -30,7 +30,7 @@ type Create struct {
|
||||
MatchRules []Match `json:"match"`
|
||||
Upstream string `json:"upstream"`
|
||||
Proxy *InputProxy `json:"proxy"`
|
||||
Disable bool `json:"disabled"`
|
||||
Disable bool `json:"disable"`
|
||||
}
|
||||
|
||||
type InputProxy struct {
|
||||
@@ -70,7 +70,7 @@ type Edit struct {
|
||||
Methods *[]string `json:"methods"`
|
||||
Protocols *[]string `json:"protocols"`
|
||||
MatchRules *[]Match `json:"match"`
|
||||
Disable *bool `json:"disabled"`
|
||||
Disable *bool `json:"disable"`
|
||||
Upstream *string `json:"upstream"`
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ type Detail struct {
|
||||
SimpleDetail
|
||||
Proxy *Proxy `json:"proxy"`
|
||||
Protocols []string `json:"protocols"`
|
||||
Disable bool `json:"disabled"`
|
||||
Disable bool `json:"disable"`
|
||||
//Doc map[string]interface{} `json:"doc"`
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ type EditService struct {
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
ServiceType *string `json:"service_type"`
|
||||
Prefix *string `json:"prefix"`
|
||||
Catalogue *string `json:"catalogue"`
|
||||
Logo *string `json:"logo"`
|
||||
Tags *[]string `json:"tags"`
|
||||
|
||||
+54
-79
@@ -10,14 +10,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
service_overview "github.com/APIParkLab/APIPark/service/service-overview"
|
||||
|
||||
"github.com/APIParkLab/APIPark/common"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/eolinker/go-common/register"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
|
||||
mcp_server "github.com/APIParkLab/APIPark/mcp-server"
|
||||
|
||||
"github.com/APIParkLab/APIPark/service/release"
|
||||
@@ -83,11 +81,12 @@ type imlServiceModule struct {
|
||||
tagService tag.ITagService `autowired:""`
|
||||
localModelService ai_local.ILocalModelService `autowired:""`
|
||||
|
||||
serviceTagService service_tag.ITagService `autowired:""`
|
||||
apiService api.IAPIService `autowired:""`
|
||||
apiDocService api_doc.IAPIDocService `autowired:""`
|
||||
clusterService cluster.IClusterService `autowired:""`
|
||||
subscribeServer subscribe.ISubscribeService `autowired:""`
|
||||
serviceOverviewService service_overview.IOverviewService `autowired:""`
|
||||
serviceTagService service_tag.ITagService `autowired:""`
|
||||
apiService api.IAPIService `autowired:""`
|
||||
apiDocService api_doc.IAPIDocService `autowired:""`
|
||||
clusterService cluster.IClusterService `autowired:""`
|
||||
subscribeServer subscribe.ISubscribeService `autowired:""`
|
||||
|
||||
releaseService release.IReleaseService `autowired:""`
|
||||
serviceModelMappingService service_model_mapping.IServiceModelMappingService `autowired:""`
|
||||
@@ -304,6 +303,7 @@ func (i *imlServiceModule) OnInit() {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, s := range services {
|
||||
err = i.updateMCPServer(ctx, s.Id, s.Name, "1.0")
|
||||
if err != nil {
|
||||
@@ -311,6 +311,28 @@ func (i *imlServiceModule) OnInit() {
|
||||
return
|
||||
}
|
||||
}
|
||||
overviews, err := i.serviceOverviewService.List(ctx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if len(overviews) > 0 {
|
||||
return
|
||||
}
|
||||
countMap, err := i.apiDocService.APICountByServices(ctx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
for k, v := range countMap {
|
||||
err = i.serviceOverviewService.Update(ctx, k, &service_overview.Update{
|
||||
ApiCount: &v,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -369,67 +391,11 @@ func (i *imlServiceModule) updateMCPServer(ctx context.Context, sid string, name
|
||||
if err != nil {
|
||||
return fmt.Errorf("get api doc commit error: %w", err)
|
||||
}
|
||||
mcpInfo, err := mcp_server.ConvertMCPFromOpenAPI3Data([]byte(commitDoc.Data.Content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert mcp from openapi3 data error: %w", err)
|
||||
}
|
||||
tools := make([]mcp_server.ITool, 0, len(mcpInfo.Apis))
|
||||
for _, a := range mcpInfo.Apis {
|
||||
toolOptions := make([]mcp.ToolOption, 0, len(a.Params)+2)
|
||||
toolOptions = append(toolOptions, mcp.WithDescription(a.Description))
|
||||
headers := make(map[string]interface{})
|
||||
queries := make(map[string]interface{})
|
||||
path := make(map[string]interface{})
|
||||
for _, v := range a.Params {
|
||||
p := map[string]interface{}{
|
||||
"type": "string",
|
||||
"required": v.Required,
|
||||
"description": v.Description,
|
||||
}
|
||||
switch v.In {
|
||||
case "header":
|
||||
headers[v.Name] = p
|
||||
case "query":
|
||||
queries[v.Name] = p
|
||||
case "path":
|
||||
path[v.Name] = p
|
||||
}
|
||||
}
|
||||
if len(headers) > 0 {
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPHeader, mcp.Properties(headers), mcp.Description("request headers.")))
|
||||
}
|
||||
if len(queries) > 0 {
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPQuery, mcp.Properties(queries), mcp.Description("request queries.")))
|
||||
}
|
||||
if len(path) > 0 {
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPPath, mcp.Properties(path), mcp.Description("request path params.")))
|
||||
}
|
||||
if a.Body != nil {
|
||||
type Schema struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Properties map[string]interface{} `mapstructure:"properties"`
|
||||
Items interface{} `mapstructure:"items"`
|
||||
}
|
||||
var tmp Schema
|
||||
err = mapstructure.Decode(a.Body, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch tmp.Type {
|
||||
case "object":
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPBody, mcp.Properties(tmp.Properties), mcp.Description("request body,it is avalible when method is POST、PUT、PATCH.")))
|
||||
case "array":
|
||||
toolOptions = append(toolOptions, mcp.WithArray(mcp_server.MCPBody, mcp.Items(tmp.Items), mcp.Description("request body,it is avalible when method is POST、PUT、PATCH.")))
|
||||
}
|
||||
}
|
||||
tools = append(tools, mcp_server.NewTool(a.Summary, a.Path, a.Method, a.ContentType, toolOptions...))
|
||||
}
|
||||
mcp_server.SetSSEServer(sid, name, version, tools...)
|
||||
return nil
|
||||
return mcp_server.SetServerByOpenapi(sid, name, version, commitDoc.Data.Content)
|
||||
}
|
||||
|
||||
func (i *imlServiceModule) deleteMCPServer(ctx context.Context, sid string) {
|
||||
mcp_server.DelSSEServer(sid)
|
||||
mcp_server.DelServer(sid)
|
||||
}
|
||||
|
||||
func (i *imlServiceModule) ExportAll(ctx context.Context) ([]*service_dto.ExportService, error) {
|
||||
@@ -510,7 +476,14 @@ func (i *imlServiceModule) SearchMyServices(ctx context.Context, teamId string,
|
||||
serviceIds := utils.SliceToSlice(services, func(p *service.Service) string {
|
||||
return p.Id
|
||||
})
|
||||
apiCountMap, err := i.apiDocService.APICountByServices(ctx, serviceIds...)
|
||||
//apiCountMap, err := i.apiDocService.APICountByServices(ctx, serviceIds...)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//serviceIds := utils.SliceToSlice(services, func(s *service.Service) string {
|
||||
// return s.Id
|
||||
//})
|
||||
overviewMap, err := i.serviceOverviewService.Map(ctx, serviceIds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -520,10 +493,12 @@ func (i *imlServiceModule) SearchMyServices(ctx context.Context, teamId string,
|
||||
if teamId != "" && model.Team != teamId {
|
||||
continue
|
||||
}
|
||||
apiCount := apiCountMap[model.Id]
|
||||
item := toServiceItem(model)
|
||||
item.ApiNum = apiCount
|
||||
item.CanDelete = apiCount == 0
|
||||
if ov, ok := overviewMap[model.Id]; ok {
|
||||
item.ApiNum = ov.ApiCount
|
||||
item.CanDelete = ov.ApiCount == 0
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
|
||||
}
|
||||
@@ -629,22 +604,21 @@ func (i *imlServiceModule) Search(ctx context.Context, teamID string, keyword st
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serviceIds := utils.SliceToSlice(list, func(s *service.Service) string {
|
||||
return s.Id
|
||||
})
|
||||
|
||||
apiCountMap, err := i.apiDocService.APICountByServices(ctx, serviceIds...)
|
||||
overviewMap, err := i.serviceOverviewService.Map(ctx, serviceIds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]*service_dto.ServiceItem, 0, len(list))
|
||||
for _, model := range list {
|
||||
apiCount := apiCountMap[model.Id]
|
||||
item := toServiceItem(model)
|
||||
item.ApiNum = apiCount
|
||||
item.CanDelete = apiCount == 0
|
||||
if v, ok := overviewMap[model.Id]; ok {
|
||||
item.ApiNum = v.ApiCount
|
||||
item.CanDelete = v.ApiCount == 0
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
return items, nil
|
||||
@@ -734,7 +708,7 @@ func (i *imlServiceModule) Create(ctx context.Context, teamID string, input *ser
|
||||
mo.AsServer = *input.AsServer
|
||||
}
|
||||
|
||||
input.Prefix = strings.Trim(strings.Trim(input.Prefix, " "), "/")
|
||||
//input.Prefix = strings.Trim(strings.Trim(input.Prefix, " "), "/")
|
||||
err := i.transaction.Transaction(ctx, func(ctx context.Context) error {
|
||||
if input.Tags != nil {
|
||||
tags, err := i.getTagUuids(ctx, input.Tags)
|
||||
@@ -839,6 +813,7 @@ func (i *imlServiceModule) Edit(ctx context.Context, id string, input *service_d
|
||||
ServiceType: serviceType,
|
||||
Catalogue: input.Catalogue,
|
||||
AdditionalConfig: &info.AdditionalConfig,
|
||||
Prefix: input.Prefix,
|
||||
ApprovalType: &approvalType,
|
||||
EnableMCP: input.EnableMCP,
|
||||
}
|
||||
|
||||
@@ -15,15 +15,23 @@ func (p *plugin) mcpAPIs() []pm3.Api {
|
||||
globalMessagePath := fmt.Sprintf("/openapi/v1/%s/message", strings.Trim(mcp_server.GlobalBasePath, "/"))
|
||||
serviceMessagePath := fmt.Sprintf("/openapi/v1/%s/:serviceId/message", strings.Trim(mcp_server.ServiceBasePath, "/"))
|
||||
serviceSSEPath := fmt.Sprintf("/openapi/v1/%s/:serviceId/sse", strings.Trim(mcp_server.ServiceBasePath, "/"))
|
||||
serviceStreamablePath := fmt.Sprintf("/openapi/v1/%s/:serviceId/mcp", strings.Trim(mcp_server.ServiceBasePath, "/"))
|
||||
ignore.IgnorePath("openapi", http.MethodPost, globalMessagePath)
|
||||
|
||||
ignore.IgnorePath("openapi", http.MethodGet, serviceSSEPath)
|
||||
ignore.IgnorePath("openapi", http.MethodPost, serviceMessagePath)
|
||||
ignore.IgnorePath("openapi", http.MethodGet, serviceStreamablePath)
|
||||
ignore.IgnorePath("openapi", http.MethodPost, serviceStreamablePath)
|
||||
ignore.IgnorePath("openapi", http.MethodDelete, serviceStreamablePath)
|
||||
|
||||
return []pm3.Api{
|
||||
pm3.CreateApiSimple(http.MethodGet, fmt.Sprintf("/openapi/v1/%s/sse", strings.Trim(mcp_server.GlobalBasePath, "/")), p.mcpController.GlobalHandleSSE),
|
||||
pm3.CreateApiSimple(http.MethodPost, globalMessagePath, p.mcpController.GlobalHandleMessage),
|
||||
pm3.CreateApiSimple(http.MethodGet, serviceSSEPath, p.mcpController.ServiceHandleSSE),
|
||||
pm3.CreateApiSimple(http.MethodPost, serviceMessagePath, p.mcpController.ServiceHandleMessage),
|
||||
|
||||
pm3.CreateApiSimple(http.MethodPost, serviceStreamablePath, p.mcpController.ServiceHandleStreamHTTP),
|
||||
pm3.CreateApiSimple(http.MethodDelete, serviceStreamablePath, p.mcpController.ServiceHandleStreamHTTP),
|
||||
pm3.CreateApiSimple(http.MethodGet, serviceStreamablePath, p.mcpController.ServiceHandleStreamHTTP),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,9 +29,11 @@ VERSION=$(gen_version)
|
||||
|
||||
|
||||
SYS_ARCH=$(arch)
|
||||
echo "SYS_ARCH: ${SYS_ARCH}"
|
||||
echo "ARCH: ${ARCH}"
|
||||
if [[ (${SYS_ARCH} == "aarch64" || ${SYS_ARCH} == "arm64") && $ARCH == "amd64" ]];then
|
||||
OPTIONS="--platform=linux/amd64"
|
||||
elif [[ ${SYS_ARCH} == "amd64" && $ARCH == "arm64" ]];then
|
||||
elif [[ (${SYS_ARCH} == "amd64" || ${SYS_ARCH} == "x86_64") && $ARCH == "arm64" ]];then
|
||||
OPTIONS="--platform=linux/arm64"
|
||||
fi
|
||||
|
||||
|
||||
@@ -47,22 +47,22 @@ if [[ ${Init} == "true" ]];then
|
||||
r=$(is_init)
|
||||
if [[ $r == "true" ]];then
|
||||
echo "Already initialized, skipping initialization."
|
||||
exit 0
|
||||
else
|
||||
wait_for_influxdb
|
||||
|
||||
wait_for_apinto
|
||||
set_cluster
|
||||
|
||||
wait_for_influxdb
|
||||
set_influxdb
|
||||
|
||||
set_loki
|
||||
set_nsq
|
||||
set_openapi_config
|
||||
# 重启apipark
|
||||
kill -9 $(pgrep apipark)
|
||||
nohup ./apipark >> run.log 2>&1 &
|
||||
fi
|
||||
wait_for_influxdb
|
||||
|
||||
wait_for_apinto
|
||||
set_cluster
|
||||
|
||||
wait_for_influxdb
|
||||
set_influxdb
|
||||
|
||||
set_loki
|
||||
set_nsq
|
||||
set_openapi_config
|
||||
# 重启apipark
|
||||
kill -9 $(pgrep apipark)
|
||||
nohup ./apipark >> run.log 2>&1 &
|
||||
fi
|
||||
|
||||
tail -F run.log
|
||||
+14
-10
@@ -5,6 +5,8 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/eolinker/eosc/log"
|
||||
|
||||
"github.com/APIParkLab/APIPark/service/universally/commit"
|
||||
"github.com/APIParkLab/APIPark/stores/api"
|
||||
"github.com/eolinker/go-common/utils"
|
||||
@@ -59,7 +61,9 @@ func (i *imlAPIDocService) APICountByServices(ctx context.Context, serviceIds ..
|
||||
if len(serviceIds) > 0 {
|
||||
w["service"] = serviceIds
|
||||
}
|
||||
now := time.Now()
|
||||
list, err := i.store.List(ctx, w)
|
||||
log.Infof("search api doc count by services, serviceIds: %v, cost: %v", serviceIds, time.Since(now))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -68,35 +72,35 @@ func (i *imlAPIDocService) APICountByServices(ctx context.Context, serviceIds ..
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (i *imlAPIDocService) UpdateDoc(ctx context.Context, serviceId string, input *UpdateDoc) error {
|
||||
func (i *imlAPIDocService) UpdateDoc(ctx context.Context, serviceId string, input *UpdateDoc) (int64, error) {
|
||||
doc, err := NewDocLoader(input.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
if err := doc.Valid(); err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
if input.Prefix != "" {
|
||||
err = doc.AddPrefixInAll(input.Prefix)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
data, err := doc.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
input.Content = string(data)
|
||||
|
||||
operator := utils.UserId(ctx)
|
||||
info, err := i.store.First(ctx, map[string]interface{}{
|
||||
"service": serviceId,
|
||||
})
|
||||
operator := utils.UserId(ctx)
|
||||
|
||||
if err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
return i.store.Insert(ctx, &api.Doc{
|
||||
return doc.APICount(), i.store.Insert(ctx, &api.Doc{
|
||||
UUID: input.ID,
|
||||
Service: serviceId,
|
||||
Content: input.Content,
|
||||
@@ -109,7 +113,7 @@ func (i *imlAPIDocService) UpdateDoc(ctx context.Context, serviceId string, inpu
|
||||
info.Updater = operator
|
||||
info.UpdateAt = time.Now()
|
||||
info.APICount = doc.APICount()
|
||||
return i.store.Save(ctx, info)
|
||||
return doc.APICount(), i.store.Save(ctx, info)
|
||||
}
|
||||
|
||||
func (i *imlAPIDocService) GetDoc(ctx context.Context, serviceId string) (*Doc, error) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
type IAPIDocService interface {
|
||||
// UpdateDoc 更新文档
|
||||
UpdateDoc(ctx context.Context, serviceId string, input *UpdateDoc) error
|
||||
UpdateDoc(ctx context.Context, serviceId string, input *UpdateDoc) (int64, error)
|
||||
// GetDoc 获取文档
|
||||
GetDoc(ctx context.Context, serviceId string) (*Doc, error)
|
||||
|
||||
|
||||
@@ -28,6 +28,43 @@ type imlReleaseService struct {
|
||||
releaseRuntime release.IReleaseRuntime `autowired:""`
|
||||
}
|
||||
|
||||
func (s *imlReleaseService) UpdateRelease(ctx context.Context, id string, update *Update) error {
|
||||
info, err := s.releaseStore.GetByUUID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if update.Version != nil {
|
||||
info.Name = *update.Version
|
||||
}
|
||||
if update.Remark != nil {
|
||||
info.Remark = *update.Remark
|
||||
}
|
||||
|
||||
_, err = s.releaseStore.Update(ctx, info)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *imlReleaseService) GetRunningList(ctx context.Context, serviceId ...string) ([]*Release, error) {
|
||||
w := make(map[string]interface{})
|
||||
if len(serviceId) > 0 {
|
||||
w["service"] = serviceId
|
||||
}
|
||||
list, err := s.releaseRuntime.List(ctx, w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
commitIds := utils.SliceToSlice(list, func(o *release.Runtime) string {
|
||||
return o.Release
|
||||
})
|
||||
commits, err := s.releaseStore.List(ctx, map[string]interface{}{
|
||||
"uuid": commitIds,
|
||||
})
|
||||
return utils.SliceToSlice(commits, FromEntity), err
|
||||
}
|
||||
|
||||
func (s *imlReleaseService) GetRunningApiDocCommits(ctx context.Context, serviceIds ...string) ([]string, error) {
|
||||
w := make(map[string]interface{})
|
||||
if len(serviceIds) > 0 {
|
||||
|
||||
@@ -26,6 +26,12 @@ func FromEntity(e *release.Release) *Release {
|
||||
}
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
Version *string
|
||||
Remark *string
|
||||
APICount *int64 // API数量
|
||||
}
|
||||
|
||||
type APICommit struct {
|
||||
Release string
|
||||
API string
|
||||
|
||||
@@ -15,12 +15,15 @@ type IReleaseService interface {
|
||||
GetRelease(ctx context.Context, id string) (*Release, error)
|
||||
// CreateRelease 创建发布
|
||||
CreateRelease(ctx context.Context, service, version, remark string, apiRequestCommit, apisProxyCommits map[string]string, apiDocCommits, serviceDocCommits string, upstreams map[string]map[string]string, strategies map[string]string) (*Release, error)
|
||||
UpdateRelease(ctx context.Context, id string, update *Update) error
|
||||
// DeleteRelease 删除发布
|
||||
DeleteRelease(ctx context.Context, id string) error
|
||||
|
||||
GetRunningApiDocCommits(ctx context.Context, serviceIds ...string) ([]string, error)
|
||||
List(ctx context.Context, service string) ([]*Release, error)
|
||||
GetReleaseInfos(ctx context.Context, id string) ([]*APICommit, []*APICommit, *APICommit, []*UpstreamCommit, *ServiceCommit, error)
|
||||
GetCommits(ctx context.Context, id string) ([]*ProjectCommits, error)
|
||||
GetRunningApiDocCommits(ctx context.Context, serviceIds ...string) ([]string, error)
|
||||
//GetRunningApiDocCommits(ctx context.Context, serviceIds ...string) ([]string, error)
|
||||
GetRunningApiProxyCommit(ctx context.Context, service string, apiUUID string) (string, error)
|
||||
Completeness(partitions []string, apis []string, requestCommits []*commit.Commit[api.Request], proxyCommits []*commit.Commit[api.Proxy], upstreamCommits []*commit.Commit[upstream.Config]) bool
|
||||
|
||||
@@ -30,6 +33,7 @@ type IReleaseService interface {
|
||||
// service: the service name
|
||||
// Return type(s): *Release, error
|
||||
GetRunning(ctx context.Context, service string) (*Release, error)
|
||||
GetRunningList(ctx context.Context, serviceId ...string) ([]*Release, error)
|
||||
|
||||
SetRunning(ctx context.Context, service string, id string) error
|
||||
CheckNewVersion(ctx context.Context, service string, version string) (bool, error)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package service_overview
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/eolinker/go-common/utils"
|
||||
|
||||
"github.com/APIParkLab/APIPark/stores/service"
|
||||
)
|
||||
|
||||
var _ IOverviewService = (*imlOverviewService)(nil)
|
||||
|
||||
type imlOverviewService struct {
|
||||
store service.IOverviewStore `autowired:""`
|
||||
}
|
||||
|
||||
func genUpdateFields(info *service.Overview, update *Update) {
|
||||
if update.ApiCount != nil {
|
||||
info.ApiCount = *update.ApiCount
|
||||
}
|
||||
if update.ReleaseApiCount != nil {
|
||||
info.ReleaseApiCount = *update.ReleaseApiCount
|
||||
}
|
||||
if update.IsReleased != nil {
|
||||
info.IsReleased = *update.IsReleased
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (i imlOverviewService) Update(ctx context.Context, serviceId string, update *Update) error {
|
||||
if update == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
info, err := i.store.First(ctx, map[string]interface{}{
|
||||
"service": serviceId,
|
||||
})
|
||||
if err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
info = &service.Overview{
|
||||
Service: serviceId,
|
||||
}
|
||||
genUpdateFields(info, update)
|
||||
return i.store.Insert(ctx, info)
|
||||
}
|
||||
genUpdateFields(info, update)
|
||||
_, err = i.store.Update(ctx, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i imlOverviewService) List(ctx context.Context, serviceIds ...string) ([]*Overview, error) {
|
||||
w := make(map[string]interface{})
|
||||
if len(serviceIds) > 0 {
|
||||
w = map[string]interface{}{
|
||||
"service": serviceIds,
|
||||
}
|
||||
}
|
||||
list, err := i.store.List(ctx, w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SliceToSlice(list, FromEntity), nil
|
||||
}
|
||||
|
||||
func (i imlOverviewService) Map(ctx context.Context, serviceIds ...string) (map[string]*Overview, error) {
|
||||
list, err := i.List(ctx, serviceIds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SliceToMap(list, func(i *Overview) string {
|
||||
return i.Service
|
||||
}), nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package service_overview
|
||||
|
||||
import (
|
||||
"github.com/APIParkLab/APIPark/stores/service"
|
||||
)
|
||||
|
||||
type Overview struct {
|
||||
Service string
|
||||
ApiCount int64
|
||||
ReleaseApiCount int64
|
||||
IsReleased bool
|
||||
}
|
||||
|
||||
func FromEntity(e *service.Overview) *Overview {
|
||||
return &Overview{
|
||||
Service: e.Service,
|
||||
ApiCount: e.ApiCount,
|
||||
ReleaseApiCount: e.ReleaseApiCount,
|
||||
IsReleased: e.IsReleased,
|
||||
}
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
ApiCount *int64
|
||||
ReleaseApiCount *int64
|
||||
IsReleased *bool
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package service_overview
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/eolinker/go-common/autowire"
|
||||
)
|
||||
|
||||
type IOverviewService interface {
|
||||
Update(ctx context.Context, serviceId string, update *Update) error
|
||||
List(ctx context.Context, serviceIds ...string) ([]*Overview, error)
|
||||
Map(ctx context.Context, serviceIds ...string) (map[string]*Overview, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
autowire.Auto[IOverviewService](func() reflect.Value {
|
||||
return reflect.ValueOf(new(imlOverviewService))
|
||||
})
|
||||
}
|
||||
@@ -229,5 +229,8 @@ func updateHandler(e *service.Service, i *Edit) {
|
||||
if i.EnableMCP != nil {
|
||||
e.EnableMCP = *i.EnableMCP
|
||||
}
|
||||
//if i.Prefix != nil {
|
||||
// e.Prefix = *i.Prefix
|
||||
//}
|
||||
e.UpdateAt = time.Now()
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ type Edit struct {
|
||||
AdditionalConfig *map[string]string
|
||||
State *int
|
||||
ApprovalType *ApprovalType
|
||||
Prefix *string
|
||||
EnableMCP *bool
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ type Doc struct {
|
||||
Content string `gorm:"type:longtext;null;column:content;comment:文档内容"`
|
||||
Updater string `gorm:"size:36;not null;column:updater;comment:更新人;index:updater" aovalue:"updater"`
|
||||
UpdateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:update_at;comment:更新时间"`
|
||||
APICount int64 `gorm:"type:int(11);not null;column:api_count;comment:接口数量"`
|
||||
APICount int64 `gorm:"type:int(11);not null;column:api_count;default:0;comment:接口数量"`
|
||||
}
|
||||
|
||||
func (i *Doc) TableName() string {
|
||||
|
||||
@@ -35,6 +35,22 @@ func (p *Service) TableName() string {
|
||||
return "service"
|
||||
}
|
||||
|
||||
type Overview struct {
|
||||
Id int64 `gorm:"type:BIGINT(20);size:20;not null;auto_increment;primary_key;column:id;comment:主键ID;"`
|
||||
Service string `gorm:"size:255;not null;column:service;comment:服务ID"`
|
||||
ApiCount int64 `gorm:"type:BIGINT(20);not null;column:api_count;comment:接口数量"`
|
||||
ReleaseApiCount int64 `gorm:"type:BIGINT(20);not null;column:release_api_count;comment:已发布接口数量"`
|
||||
IsReleased bool `gorm:"type:tinyint(1);not null;column:is_released;comment:是否已发布"`
|
||||
}
|
||||
|
||||
func (o *Overview) IdValue() int64 {
|
||||
return o.Id
|
||||
}
|
||||
|
||||
func (o *Overview) TableName() string {
|
||||
return "service_overview"
|
||||
}
|
||||
|
||||
type Authorization struct {
|
||||
Id int64 `gorm:"type:BIGINT(20);size:20;not null;auto_increment;primary_key;column:id;comment:主键ID;"`
|
||||
UUID string `gorm:"size:36;not null;column:uuid;uniqueIndex:uuid;comment:UUID;"`
|
||||
|
||||
@@ -13,6 +13,12 @@ type IServiceStore interface {
|
||||
type imlServiceStore struct {
|
||||
store.SearchStore[Service]
|
||||
}
|
||||
type IOverviewStore interface {
|
||||
store.IBaseStore[Overview]
|
||||
}
|
||||
type imlOverviewStore struct {
|
||||
store.Store[Overview]
|
||||
}
|
||||
|
||||
type IServiceTagStore interface {
|
||||
store.IBaseStore[Tag]
|
||||
@@ -61,6 +67,9 @@ func init() {
|
||||
return reflect.ValueOf(new(imlServiceDocStore))
|
||||
})
|
||||
|
||||
autowire.Auto[IOverviewStore](func() reflect.Value {
|
||||
return reflect.ValueOf(new(imlOverviewStore))
|
||||
})
|
||||
autowire.Auto[IServiceModelMappingStore](func() reflect.Value {
|
||||
return reflect.ValueOf(new(imlServiceModelMappingStore))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user