frontend: update service & support more language

This commit is contained in:
maggieyyy
2024-10-17 18:42:52 +08:00
parent cd4795d50a
commit 83306364ce
69 changed files with 4200 additions and 658 deletions
-6
View File
@@ -1,9 +1,3 @@
<!--
* @Date: 2024-06-05 16:00:58
* @LastEditors: maggieyyy
* @LastEditTime: 2024-07-12 20:15:05
* @FilePath: \frontend\README.md
-->
# 部署
## 安装依赖
+65 -18
View File
@@ -1,24 +1,37 @@
const fs = require('fs');
const path = require('path');
const { crc32 } = require('crc');
const systemLanguage = {
en_US: 'en-US',
zh_CN: 'zh-CN',
en_GB: 'en-GB'
ja_JP: 'ja-JP',
zh_TW: 'zh-TW'
};
// 读取已经存在在en.json文件的词条
const localesDir = 'packages/common/src/locales/scan';
const newJsonDir = 'packages/common/src/locales/scan/newJson';
const keyHashFile = 'packages/common/src/locales/keyHashMap.json';
let existData = {};
try {
console.log('Current working directory:', process.cwd());
const existJsonData = fs.readFileSync('packages/common/src/locales/scan/en-GB.json');
existData = JSON.parse(existJsonData);
} catch (error) {
if (error.code === 'ENOENT') {
console.warn('File not found: packages/common/src/locales/scan/en-GB.json. Creating an empty file.');
fs.writeFileSync('packages/common/src/locales/scan/en-GB.json', JSON.stringify({}));
} else {
throw error;
let keyHashMap = {};
fs.readdirSync(localesDir).forEach(file => {
if (path.extname(file) === '.json') {
const lang = path.basename(file, '.json');
const filePath = path.join(localesDir, file);
try {
console.log('Current working directory:', process.cwd(),filePath);
const existJsonData = fs.readFileSync(filePath);
existData[lang] = JSON.parse(existJsonData);
} catch (error) {
if (error.code === 'ENOENT') {
console.warn(`File not found: ${filePath}. Creating an empty file.`);
fs.writeFileSync(filePath, JSON.stringify({}));
existData[lang] = {};
} else {
throw error;
}
}
}
}
});
const keyList = Object.keys(existData);
@@ -34,7 +47,7 @@ module.exports = {
debug: true,
func: false,
trans: false,
lngs: [systemLanguage.zh_CN, systemLanguage.en_GB],
lngs: [systemLanguage.zh_CN, systemLanguage.en_US, systemLanguage.ja_JP, systemLanguage.zh_TW],
defaultLng: systemLanguage.zh_CN,
resource: {
loadPath: './newJson/{{lng}}.json', // 输入路径 (手动新建目录)
@@ -58,11 +71,45 @@ module.exports = {
parser.parseFuncFromString(content, { list: ['t'] }, (key, options) => {
options.defaultValue = key;
const hashKey = `K${crc32(key).toString(16)}`; // crc32转换格式
// 如果词条不存在,则写入
if (!keyList.includes(hashKey)) {
parser.set(hashKey, options);
}
keyHashMap[key] = hashKey;
// 遍历每种语言,逐个语言检查翻译是否存在
keyList.forEach((lng) => {
const langData = existData[lng] || {};
// 如果某语言没有翻译该字段,则记录到该语言的 newJson 文件中
if (!langData[hashKey]) {
const newJsonPath = path.join(newJsonDir, `${lng}.json`);
let newJsonData = {};
// 读取当前语言的 newJson 文件,如果已存在,则加载
try {
const newJsonRaw = fs.readFileSync(newJsonPath);
newJsonData = JSON.parse(newJsonRaw);
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
// 只添加尚未存在的 key
if (!newJsonData[hashKey]) {
newJsonData[hashKey] = options.defaultValue; // 使用原始 key 作为默认值
fs.writeFileSync(newJsonPath, JSON.stringify(newJsonData, null, 2));
}
}
});
// // 如果词条不存在,则写入
// if (!keyList.includes(hashKey)) {
// parser.set(hashKey, options);
// }
});
done();
},
flush: function(done) {
// 将 keyHashMap 写入文件
fs.writeFileSync(keyHashFile, JSON.stringify(keyHashMap, null, 2));
done();
}
};
@@ -248,8 +248,8 @@ const PUBLIC_ROUTES:RouteConfig[] = [
}]
},
{
path:'servicecategories',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/serviceCategory/ServiceCategory.tsx')),
path:'commonsetting',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/common/CommonPage.tsx')),
key:uuidv4(),
},
{
@@ -49,31 +49,27 @@ const themeToken = {
getNavItem($t('工作空间'), 'workspace','/guide/page',<Icon icon="ic:baseline-space-dashboard" width="18" height="18"/>, [
getNavItem(<a>{$t('首页')}</a>, 'guide','/guide/page',<Icon icon="ic:baseline-home" width="18" height="18"/>,undefined,undefined,'all'),
getNavItem(<a>{$t('应用')}</a>, 'tenantManagement','/tenantManagement',<Icon icon="ic:baseline-apps" width="18" height="18"/>,undefined,undefined,'all'),
getNavItem(<a>{$t('服务')}</a>, 'service','/service',<Icon icon="ic:baseline-blinds-closed" width="18" height="18"/>,undefined,undefined,'all'),
getNavItem(<a>{$t('团队')}</a>, 'team','/team',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,'all'),
getNavItem($t('服务'), 'my','/service',null,[
getNavItem(<a>{$t('REST 服务')}</a>, 'service','/service',<Icon icon="ic:baseline-blinds-closed" width="18" height="18"/>,undefined,undefined,''),
getNavItem(<a>{$t('AI 服务')}</a>, 'aiservice','/aiservice',<Icon icon="eos-icons:ai" width="18" height="18"/>,undefined,undefined,''),
],undefined,''),
]),
getNavItem(<a>{$t('API 市场')}</a>, 'serviceHub','/serviceHub',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.workspace.api_market.view'),
getNavItem($t('API 市场'), 'serviceHub','/serviceHub',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.workspace.api_market.view'),
getNavItem($t('仪表盘'), 'mainPage', APP_MODE === 'pro' ? '/analytics' : '/analytics/total',<Icon icon="ic:baseline-bar-chart" width="18" height="18"/>,[
getNavItem(<a >{$t('运行视图')}</a>, 'analytics',APP_MODE === 'pro' ? '/analytics' : '/analytics/total' ,<ProjectFilled />,undefined,undefined,'system.dashboard.run_view.view'),
APP_MODE === 'pro' ? getNavItem(<a >{$t('系统拓扑图')}</a>, 'systemrunning','/systemrunning',<ProjectFilled />,undefined,undefined,'system.dashboard.systemrunning.view') : null,
],undefined,'system.dashboard.run_view.view'),
getNavItem($t('系统设置'), 'operationCenter','/member',<Icon icon="ic:baseline-settings" width="18" height="18"/>, [
getNavItem($t('组织'), 'organization','/member',null,[
getNavItem(<a>{$t('成员')}</a>, 'member','/member',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,'system.organization.member.view'),
getNavItem($t('系统设置'), 'operationCenter','/commonsetting',<Icon icon="ic:baseline-settings" width="18" height="18"/>, [
getNavItem($t('系统'), 'serviceHubSetting','/commonsetting',null,[
getNavItem(<a>{$t('常规')}</a>, 'commonsetting','/commonsetting',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.api_market.service_classification.view'),
getNavItem(<a>{$t('API 网关')}</a>, 'cluster','/cluster',<Icon icon="ic:baseline-device-hub" width="18" height="18"/>,undefined,undefined,'system.devops.cluster.view'),
getNavItem(<a>{$t('AI 模型')}</a>, 'aisetting','/aisetting',<Icon icon="hugeicons:ai-network" width="18" height="18"/>,undefined,undefined,'system.devops.cluster.view'),
],undefined,'system.api_market.service_classification.view'),
getNavItem($t('用户'), 'organization','/member',null,[
getNavItem(<a>{$t('账号')}</a>, 'member','/member',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,'system.organization.member.view'),
getNavItem(<a>{$t('角色')}</a>, 'role','/role',<Icon icon="ic:baseline-verified-user" width="18" height="18"/>,undefined,undefined,'system.organization.role.view'),
],undefined,''),
getNavItem($t('API 市场'), 'serviceHubSetting','/servicecategories',null,[
getNavItem(<a>{$t('服务分类管理')}</a>, 'servicecategories','/servicecategories',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.api_market.service_classification.view'),
],undefined,'system.api_market.service_classification.view'),
getNavItem($t('运维与集成'), 'maintenanceCenter','/cluster', null, [
getNavItem(<a>{$t('集群')}</a>, 'cluster','/cluster',<Icon icon="ic:baseline-device-hub" width="18" height="18"/>,undefined,undefined,'system.devops.cluster.view'),
getNavItem(<a>{$t('AI 配置')}</a>, 'aisetting','/aisetting',<Icon icon="hugeicons:ai-network" width="18" height="18"/>,undefined,undefined,'system.devops.cluster.view'),
getNavItem($t('集成'), 'maintenanceCenter','/datasourcing', null, [
getNavItem(<a>{$t('数据源')}</a>, 'datasourcing','/datasourcing',<Icon icon="ic:baseline-monitor-heart" width="18" height="18"/>,undefined,undefined,'system.devops.data_source.view'),
getNavItem(<a>{$t('证书')}</a>, 'cert','/cert',<Icon icon="ic:baseline-security" width="18" height="18"/>,undefined,undefined,'system.devops.ssl_certificate.view'),
getNavItem(<a>{$t('日志')}</a>, 'logsettings','/logsettings',<Icon icon="ic:baseline-sticky-note-2" width="18" height="18"/>,undefined,undefined,'system.devops.log_configuration.view'),
@@ -171,14 +167,14 @@ const themeToken = {
{
key: '2',
label: (
<Button key="changePsw" type="text" className="border-none p-0 flex items-center bg-transparent " onClick={()=>navigator('/userProfile/changepsw')}>
<Button key="changePsw" type="text" className="flex items-center p-0 bg-transparent border-none " onClick={()=>navigator('/userProfile/changepsw')}>
{$t('账号设置')}
</Button>)
},
{
key: '3',
label: (
<Button key="logout" type="text" className="border-none p-0 flex items-center bg-transparent " onClick={logOut}>
<Button key="logout" type="text" className="flex items-center p-0 bg-transparent border-none " onClick={logOut}>
{$t('退出登录')}
</Button>)
},
@@ -200,7 +196,6 @@ const themeToken = {
return document.getElementById('test-pro-layout') || document.body;
}}
>
<ProLayout
prefixCls="apipark-layout"
location={{
@@ -8,20 +8,34 @@ import { Icon } from '@iconify/react/dist/iconify.js';
const LanguageSetting = ({mode = 'light'}:{mode?:'dark'|'light'}) => {
const { dispatch,state} = useGlobalContext()
const items = [
{
key: 'cn',
label: <Button key="cn" type="text" className="border-none p-0 flex items-center bg-transparent ">
{$t('简体')}
{
key: 'en-US',
label:<Button key="en" type="text" className="border-none p-0 flex items-center bg-transparent ">
English
</Button>,
title: $t('简体'),
},
{
key: 'en',
label:<Button key="en" type="text" className="border-none p-0 flex items-center bg-transparent ">
{$t('英文')}
</Button>,
title:$t('英文')
}
title:'English'
},
{
key: 'ja-JP',
label: <Button key="jp" type="text" className="border-none p-0 flex items-center bg-transparent ">
</Button>,
title: '日本語',
},
{
key: 'zh-TW',
label: <Button key="tw" type="text" className="border-none p-0 flex items-center bg-transparent ">
</Button>,
title: '繁體中文',
},
{
key: 'zh-CN',
label: <Button key="cn" type="text" className="border-none p-0 flex items-center bg-transparent ">
</Button>,
title: '简体中文',
},
];
const langLabel = useMemo(()=>items.find((item) => item?.key === state.language)?.title,[state.language])
@@ -30,9 +44,9 @@ const LanguageSetting = ({mode = 'light'}:{mode?:'dark'|'light'}) => {
const savedLang = sessionStorage.getItem('i18nextLng')
const browserLang = navigator.language || navigator.userLanguage
if(savedLang){
dispatch({ type: 'UPDATE_LANGUAGE', language: savedLang.startsWith('cn') ? 'cn' : 'en' });
dispatch({ type: 'UPDATE_LANGUAGE', language: savedLang });
}else{
dispatch({ type: 'UPDATE_LANGUAGE', language: browserLang.startsWith('zh') ? 'cn' : 'en' });
dispatch({ type: 'UPDATE_LANGUAGE', language: browserLang });
}
},[
])
@@ -1,15 +1,65 @@
import { $t } from "@common/locales"
/* 本组件不在页面渲染,只是为了让i18next-scanner能找到从接口传递的、需要翻译的字段 */
/* 本组件不在页面渲染,只是为了让i18next-scanner能找到从接口传递的、需要翻译的字段 , 此处的字段除非确认不在页面上渲染了,否则不应删除,容易导致翻译遗漏*/
export const TranslateWord = ()=>{
return (
<>
<>
{$t('文件日志')}
{$t('HTTP日志')}
{$t('Kafka日志')}
{$t('NSQ日志')}
{$t('Syslog日志')}
{$t('未分配')}
{$t('超级管理员')}
{$t('团队管理员')}
{$t('运维管理员')}
{$t('普通成员')}
{$t('只读成员')}
{$t('服务管理员')}
{$t('服务开发者')}
{$t('应用开发者')}
{$t('应用管理员')}
{$t('驱动名称')}
{$t('请求失败数')}
{$t('转发失败数')}
{$t('作用范围')}
{$t('添加条目')}
{$t('添加地址')}
{$t('文件名称')}
{$t('存放目录')}
{$t('日志分割周期')}
{$t('过期时间')}
{$t('单位:天')}
{$t('输出格式')}
{$t('格式化配置')}
{$t('服务器地址')}
{$t('Access日志')}
{$t('NSQD地址列表')}
{$t('鉴权Secret')}
{$t('网络协议')}
{$t('日志等级')}
{$t('单行')}
{$t('小时')}
{$t('天')}
{$t('未发布')}
{$t('待发布')}
{$t('单位:s,最小值:1')}
{$t('上传文件')}
{$t('替换文件')}
{$t('是否放行')}
{$t('监控')}
{$t('必填')}
{$t('字符非法,仅支持英文')}
{$t('上传 OpenAPI 文档 (.json/.yaml)')}
{$t('替换 OpenAPI 文档 (.json/.yaml)')}
{$t('打开 OpenAPI YAML 编辑器')}
{$t('无需审批:允许任何应用调用该服务')}
{$t('人工审批:仅允许通过人工审批的应用调用该服务')}
{$t('永久')}
{$t('否')}
{$t('是')}
{$t('无需审批')}
{$t('需要审批')}
</>
)
}
@@ -32,13 +32,14 @@ const WithPermission = ({access, tooltip, children,disabled, showDisabled = true
return (
<>
<>{
}
{editAccess && !disabled && cloneElement(children)}
{editAccess && disabled && <Tooltip title={tooltip}>
{ cloneElement(children, {disabled:true})}
</Tooltip>}
{!editAccess && (children?.type !== Button && children?.type !== Upload && showDisabled ) && <Tooltip title={tooltip ?? $t("暂无操作权限,请联系管理员分配。")}>
{ cloneElement(children, {disabled:true})}
{!editAccess && (children?.type !== Button && children?.type !== Upload && showDisabled) && <Tooltip title={tooltip ?? $t("暂无操作权限,请联系管理员分配。")}>
{ cloneElement(children, {disabled:true,okButtonProps:{disabled:true}})}
</Tooltip>}
</>
@@ -176,7 +176,7 @@ export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle
const { state } = useGlobalContext()
useEffect(()=>{
setValidateLanguage(state.language === 'cn' ? 'zh-CN' : 'en-US')
setValidateLanguage(state.language)
},[state.language])
const pluginEditSchema = {
+2 -2
View File
@@ -23,9 +23,9 @@ export const routerKeyMap = new Map<string, string[]|string>([
['workspace',['tenantManagement','service','team','guide']],
['my',['tenantManagement','service','team']],
['mainPage',['dashboard','systemrunning']],
['operationCenter',['member','user','role','servicecategories']],
['operationCenter',['member','user','role','common']],
['organization',['member','user','role']],
['serviceHubSetting',['servicecategories']],
['serviceHubSetting',['common']],
['maintenanceCenter',['aisetting','datasourcing','cluster','cert','logsettings','resourcesettings','openapi']
]])
@@ -121,22 +121,32 @@ export const PERMISSION_DEFINITION = [
},
"system.api_market.service_classification.view": {
"granted": {
"anyOf": [{ "backend": ["system.api_market.service_classification.view"] }]
"anyOf": [{ "backend": ["system.settings.service_classification.view"] }]
}
},
"system.api_market.service_classification.add": {
"granted": {
"anyOf": [{ "backend": ["system.api_market.service_classification.manager"] }]
"anyOf": [{ "backend": ["system.settings.service_classification.manager"] }]
}
},
"system.api_market.service_classification.edit": {
"granted": {
"anyOf": [{ "backend": ["system.api_market.service_classification.manager"] }]
"anyOf": [{ "backend": ["system.settings.service_classification.manager"] }]
}
},
"system.api_market.service_classification.delete": {
"granted": {
"anyOf": [{ "backend": ["system.api_market.service_classification.manager"] }]
"anyOf": [{ "backend": ["system.settings.service_classification.manager"] }]
}
},
"system.devops.system_setting.view": {
"granted": {
"anyOf": [{ "backend": ["system.settings.general.view"] }]
}
},
"system.devops.system_setting.edit": {
"granted": {
"anyOf": [{ "backend": ["system.settings.general.manager"] }]
}
},
"system.dashboard.run_view.view":{
@@ -459,6 +469,16 @@ export const PERMISSION_DEFINITION = [
"anyOf": [{ "backend": ["team.application.authorization.manager"] }]
}
},
"team.application.authorization.cancelSubApply": {
"granted": {
"anyOf": [{ "backend": ["team.application.authorization.manager"] }]
}
},
"team.application.authorization.cancelSub": {
"granted": {
"anyOf": [{ "backend": ["team.application.authorization.manager"] }]
}
},
"team.team.team.view": {
"granted": {
"anyOf": [{ "backend": ["team.team.team.view"] }]
@@ -5,19 +5,19 @@ import LanguageDetector from 'i18next-browser-languagedetector';
import crc32 from 'crc/crc32';
// 引入需要实现国际化的简体、繁体、英文三种数据的json文件
import zhCN from 'antd/locale/zh_CN';
import enGB from 'antd/locale/en_GB';
import enUS from 'antd/locale/en_US';
import localZh_CN from './scan/zh-CN.json'; // 本地翻译中文文件
import localEn_GB from './scan/en-GB.json'; // 本地翻译英文文件
import localEn_US from './scan/en-US.json'; // 本地翻译英文文件
// import config from '../../../../i18next-scanner.config.js';
const resources = {
cn: {
'zh-CH': {
translation: localZh_CN,
...zhCN
},
en: {
translation: localEn_GB,
...enGB
'en-US': {
translation: localEn_US,
...enUS
}
};
@@ -0,0 +1,580 @@
{
"工作空间": "Kc0e5ef9f",
"首页": "K4de11e23",
"应用": "Kfe93ef35",
"服务": "Kb58e0c3f",
"团队": "Kc9e489f5",
"API 市场": "K61c89f5f",
"仪表盘": "K16d71239",
"运行视图": "K714c192d",
"系统拓扑图": "Kd57dfe97",
"系统设置": "K3fe97dcc",
"系统": "Kecbb0e45",
"常规": "Ka358e23d",
"API 网关": "K449058e9",
"AI 模型": "K99935e6f",
"用户": "K1deaa2dd",
"账号": "K80a560a1",
"角色": "Kf644225f",
"集成": "K4057391a",
"数据源": "K8fa58214",
"证书": "K481e8a05",
"日志": "Kca53edd0",
"资源": "Kb283e720",
"Open API": "K631d646f",
"账号设置": "K6535ff9c",
"退出登录": "Kf15499b4",
"文档": "Kabbd6e6",
"APIPark - 企业API数据开放平台": "K1196b104",
"HTTP 状态码": "K1f42de3",
"系统状态码": "K4770dff4",
"描述": "Kf89e58f1",
"提交": "K9e53c664",
"上一步": "Kf8e7294c",
"取消": "Ka0451c97",
"关闭": "Kb1dedda3",
"添加配置": "Kb2fc7600",
"编辑配置": "K4e07217d",
"编辑(0)": "K4ea968fe",
"添加(0)": "Ka7aaaeb",
"请输入Key": "Kaff78ecf",
"请输入Value": "K65d46535",
"返回": "Kc14b2ea3",
"ID": "K11d3633a",
"名称": "Kbff43de3",
"Driver": "K16ca79ef",
"已发布": "K7a369eef",
"下线": "Kcfa1a4d2",
"上线": "K771dc3b7",
"查看": "K530f5951",
"删除": "Kecbd7449",
"确认": "K1cbe2507",
"搜索(0)名称": "K48325b6",
"上下文": "Kc6340091",
"查询内容": "K74ecb1fa",
"会话历史": "K79f2e2f9",
"添加新变量": "K3a8912ee",
"添加工具": "Kb291a19",
"AI 模型调用默认仅使用 Query 变量,可输入 “{” 增加新变量。": "K27ece71d",
"静态上游": "Kdeed8399",
"该 API 缺失(0)(1)(2)请先补充": "K4ee62e8",
"转发信息,": "K385591f3",
"文档信息,": "K68415c14",
"上游信息,": "K133b75e9",
"成功": "K43fcaf94",
"上线失败": "Kc71c6a9",
"失败": "K56c686f8",
"申请系统": "K1ff96ff",
"所属团队": "K9bf855d6",
"申请人": "K11b994ed",
"申请时间": "K939baba7",
"版本号": "Kdab2e63b",
"版本说明": "K8b29c460",
"路由列表": "K4758140d",
"上游列表": "K54e44357",
"备注": "Kb8e8e6f5",
"上线情况": "K7e52ffa3",
"申请原因": "K1ab0ae5b",
"审核意见": "K53c00c3c",
"暂无(0)权限,请联系管理员分配。": "Kfd50704d",
"时间": "K7edf331d",
"近1小时": "Kef45b208",
"近24小时": "K9dbf22b8",
"近3天": "K820fbfab",
"近7天": "Kd6d28fc",
"文件日志": "K3d7465f7",
"HTTP日志": "Kc87167a0",
"Kafka日志": "K54630fe8",
"NSQ日志": "Kd5c3966e",
"Syslog日志": "K2e3de2c1",
"未分配": "K48322168",
"超级管理员": "K98f247f9",
"团队管理员": "K9c8a571f",
"运维管理员": "K929b485b",
"普通成员": "K82cc5ec2",
"只读成员": "Ke41d7451",
"服务管理员": "Kf99e8b66",
"服务开发者": "Kda8db57a",
"应用开发者": "K216a1ac7",
"应用管理员": "K27924db",
"驱动名称": "K8dc5c723",
"请求失败数": "Kda249fe8",
"转发失败数": "Kcf2df651",
"作用范围": "K7e6a859d",
"添加条目": "K3a008b34",
"添加地址": "Ke0599ef7",
"文件名称": "K48d3b5c4",
"存放目录": "Kafde0d2a",
"日志分割周期": "Kfb2926ac",
"过期时间": "K1a78e6f0",
"单位:天": "Kd96c2c69",
"输出格式": "Kc2b776fa",
"格式化配置": "K7b7cdac2",
"服务器地址": "K2f59807a",
"Access日志": "Kb1cfa6e7",
"NSQD地址列表": "K540488a8",
"鉴权Secret": "K8bc33a11",
"网络协议": "K1cd3002f",
"日志等级": "Kdfaa32c8",
"单行": "Kc0408d9c",
"小时": "Ke3db239d",
"天": "K3509a9f8",
"未发布": "Kb3960e83",
"待发布": "K8bd1e18",
"单位:s,最小值:1": "K225a6c43",
"上传文件": "Ke00c858c",
"替换文件": "K6d9dd1f5",
"是否放行": "K71753476",
"监控": "K597435c5",
"必填": "Ke66a17dd",
"字符非法,仅支持英文": "K28b68036",
"上传 OpenAPI 文档 (.json/.yaml)": "K6206e4ad",
"替换 OpenAPI 文档 (.json/.yaml)": "Kfba46e6d",
"打开 OpenAPI YAML 编辑器": "Kdac8ce7e",
"无需审批:允许任何应用调用该服务": "Kf5da1284",
"人工审批:仅允许通过人工审批的应用调用该服务": "Kc59ff06d",
"永久": "Kbfe02d7f",
"否": "K1e9c479e",
"是": "Kaddfcb6b",
"无需审批": "K6a7fa303",
"需要审批": "Kd196e8a4",
"暂无操作权限,请联系管理员分配。": "K23fda291",
"微信小程序": "K4618cb0a",
"获取文件,需填路径": "Ka854f511",
"暂不支持生成非 HTTPS 或非 HTTP 协议的代码示例": "Kaa11a695",
"搜索编程语言...": "Kbe46924e",
"编程语言": "Ke8e4f258",
"成功示例": "K29c07a47",
"失败示例": "K1f5c814d",
"默认 text/html;charset=UTF-8": "K4ef022d7",
"暂未填写示例": "Kd061b5bf",
"Binary": "Kc14cec33",
"请求头部": "K48b4d9e3",
"请求体": "Kcd347eaf",
"Query 参数": "K9e100bfe",
"REST 参数": "K3e9f12fd",
"api request editor": "K2bfa290c",
"返回头部": "Kb36d111a",
"返回值": "K980bde79",
"更多设置": "Kb04d201a",
"添加子参数": "Kee74f5b4",
"向下添加行": "Kc7d3106c",
"标签": "Keaabd222",
"参数名": "K8ad2c50e",
"类型": "K67d68dd1",
"必需": "K29245f47",
"示例": "Ke32cbcd3",
"输入 URL 或 cURL": "Kc13936c6",
"HTTP": "Ka1ede006",
"参数位置": "K152ac44e",
"匹配类型": "K1660ae72",
"参数值": "K91ced765",
"操作类型": "K5b265628",
"新增或修改": "K1826982d",
"匹配参数值": "Kd65b55f5",
"转发上游路径": "K15f35bf2",
"请求超时时间": "K79dec0dd",
"绑定上游服务": "K7d465645",
"重试时间": "K63a6404d",
"转发上游请求头": "K47740727",
"More": "K2b605d42",
"导入": "K1df9fbd5",
"导入格式": "K5e85df18",
"全量替换": "K9eaf7885",
"在末端插入": "Kf8c3a80b",
"增量更新": "Kd96b2d7d",
"请求头": "Kf2fc08eb",
"Rest 参数": "Ka45f1d8",
"大小": "K94bb113a",
"另存为文件": "K359919b5",
"响应": "K38bf1b90",
"响应头": "K59f4186e",
"正文": "K5f1e23fd",
"发送(Enter)": "Kf404ef7d",
"中止": "K2dbfd648",
"秒": "Kacabc771",
"复制": "K13ae6a93",
"格式化": "Ke54a14a3",
"搜索": "K43934f6d",
"替代": "K741decac",
"确定": "Kd507abff",
"The (0) must not be negative.": "Kca2d1624",
"The (0) must be greater than or equal to the (1).": "K792b255a",
"值枚举": "Kf0bed26d",
"枚举": "K633a03ca",
"最小长度": "Kd2766caf",
"最大长度": "Kd6d52485",
"最小值": "Kea15f66c",
"最大值": "K1af340ff",
"将文件拖拽至此处上传,或点击选择文件上传": "K68691e16",
"Upload Files": "Kcec46ae",
"Files Selected": "K760fb044",
"请填写接口名称": "Kea2bdee0",
"详细说明": "K49053438",
"高级匹配": "K148f6fa4",
"转发配置": "K3ae4c789",
"请求参数": "K2f4d0a37",
"返回示例": "Kde2d6dbd",
"测试 API": "K70e6069c",
"请求 Header": "Ke4603448",
"请求 Body": "K89fd86b3",
"请求示例代码": "K8747e3c4",
"响应示例": "K8613e6e7",
"响应 Header": "Kab1c2159",
"响应 Body": "Kd2be51d1",
"默认工作表": "K2a3f24ac",
"至": "K7e1ab4b0",
"详情": "Kf1b166e7",
"暂不支持带有双斜杠//的url": "K28555332",
"必填项": "K71661ee8",
"不是有效邮箱地址": "Kcbee3f8",
"最近一次更新者": "K617f34f1",
"最近一次更新时间": "K6ebca204",
"保存": "Kabfe9512",
"API 路由": "K51d1eb5d",
"API 文档": "Ka2b6d281",
"使用说明": "Kdefa9caa",
"发布": "K36856e71",
"订阅管理": "K6382bbfd",
"订阅审批": "K2eef4e4",
"订阅方管理": "Ka97bd9e5",
"管理": "K5974bf24",
"调用拓扑图": "K3fa5c4c3",
"设置": "Kb5c7b82d",
"服务 ID": "K1e84ad04",
"新增订阅方": "K39ab0358",
"手动添加": "K18307d56",
"订阅申请": "K705fe9f5",
"订阅方": "K3a67ea90",
"AI 路由设置": "Kefa2a4cf",
"路由名称": "K66060758",
"请求路径": "K5582ac8",
"提示词": "K2bb86fb4",
"变量": "K13ffbe88",
"输入这个接口的描述": "K79c8cfaf",
"重试次数": "K469e475a",
"模型配置": "K8a35059b",
"路由": "Kf9dcef3a",
"添加路由": "K6134bbe8",
"输入 URL 查找路由": "Kf85b83a0",
"模型供应商": "Kcf9f90b8",
"模型": "Kfede1c7c",
"参数": "Ke99513a0",
"审批": "K3818f03d",
"通过": "K54e27f57",
"拒绝": "K8582af3f",
"发布结果": "Kd568e15c",
"查看详情": "K35f990b0",
"申请发布": "Kdbc1f6cb",
"回滚": "Kb6860a3f",
"请确认是否回滚?": "Ka3494f4b",
"撤销申请": "Kb397a99f",
"请确认是否撤销申请?": "K7d401c0f",
"终止发布": "Ke1b79b93",
"请确认是否终止发布?": "Ka2449180",
"新建版本": "K2cb02f38",
"从 (0) 获取 API KEY": "Kb3e34847",
"已配置": "K66a7d24c",
"未配置": "Kaf074220",
"默认": "Kd9a46c29",
"AI 模型管理": "K7ac2be34",
"配置好 AI 模型后,你可以使用对应的大模型来创建 AI 服务": "K2260837a",
"同步最新模型": "K18dccc1a",
"待审批": "K6208054",
"已审批": "K74ab00a3",
"发布申请": "K56b4254f",
"API 调用地址": "Kea2f9279",
"API base URL 一般设置为API 网关的外部网络访问地址,或者是API网关绑定的域名。": "K7fc496a1",
"常规设置": "K8ab0fc95",
"API 请求设置": "Kb66fec9d",
"服务分类": "K4de0af74",
"添加子分类": "Kb4ceecea",
"修改分类名称": "K67479e88",
"添加分类": "K2bc75e2c",
"重命名分类": "Kab4aab44",
"分类名称": "Ke595a20a",
"父分类 ID": "K9679728f",
"子分类名称": "K9b2d08fd",
"快速接入 AI": "K71671763",
"配置你的 AI 模型": "Ka8a5ec5",
"通过 APIPark 快速接入各种 AI 模型,使用统一的格式来调用API,并且可以随意切换模型。": "K10d7e99f",
"创建 AI 服务和 API": "Kc057704a",
"创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。": "K76bb4a09",
"创建调用 Token": "K71b2c70f",
"为了安全地调用 API,你需要创建一个应用以及Token。": "K9bdd8403",
"调用": "Kc5738b6c",
"现在你可以通过 Token 来调用这些 API。": "Kd6d7ca1f",
"快速接入 REST API": "K86cf95f",
"创建 REST 服务和 API": "K7a3a8417",
"统计 API 调用情况": "K4a84214e",
"仪表盘中提供了多种统计图表,帮助我们了解 API 的运行情况。": "K297d8563",
"核心功能": "K2cdbb773",
"账号与角色": "K3378c50d",
"邀请你的团队成员加入 APIPark,共同管理和调用 API。": "Kda5bb930",
"团队中包含了人员、应用和服务,不同团队之间的应用和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。": "Kc8239422",
"服务内包含一组 API,并且可以发布到 API 市场被其他团队使用。": "Kd5be0cd7",
"权限管理": "K62e89ee7",
"订阅服务": "K8f7808e6",
"如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审批后才可发起 API 请求。": "Kb0755523",
"审批订阅申请": "Kd28a1aa5",
"提供服务的团队可以审批来自其他团队的订阅申请,审批通过后的应用才可发起 API 请求。": "K1c15bb2e",
"APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。": "K3453272",
"Hello!欢迎使用 APIPark": "Kd518ba3e",
"你能通过 APIPark 快速在企业内部构建 API 开放门户/市场,享受极致的转发性能、API 可观测、服务治理、多租户管理、订阅审批流程等诸多好处。": "Ke66e4182",
"如果你喜欢我们的产品,欢迎给我们 Star 或提供产品反馈意见。": "Kedd41c18",
"快速入门": "Kef02fd87",
"我们提供了一些任务来帮你快速了解 APIPark": "K43a3b38d",
"进阶教程": "K408bfcf1",
"了解 APIPark 如何更好地管理 API 和 AI": "K1afaf20e",
"了解更多功能": "K48f7e21f",
"隐藏该教程": "K698296e2",
"登录": "Kd2c1a316",
"请输入账号": "Kf076f63c",
"请输入密码": "K25c895d5",
"密码": "K551b0348",
"访客模式": "K192b3e38",
"您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。": "K91aa4801",
"Version (0)-(1)": "K480045ce",
"日志配置": "Kadee8e49",
"提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。": "K2724314b",
"部门名称": "K33c76dbc",
"父部门 ID": "K84829ca9",
"子部门名称": "K4d7fc74b",
"用户名": "Keb9fcdad",
"邮箱": "Kc654b275",
"部门": "Kbe2ecc69",
"未激活、已禁用的成员无法加入到部门": "Ka16e6c44",
"请选择成员需要新加入的部门": "Ked03ba97",
"添加账号": "K184d3473",
"编辑成员信息": "K1ecb35f2",
"加入部门": "Ke6f00b44",
"确定删除成员?此操作无法恢复,确认操作?": "K501cb1e7",
"成员与部门": "Kf20863b5",
"启用": "K52c8a730",
"禁用": "K718c9310",
"输入用户名、邮箱查找成员": "K5f27a546",
"移出当前部门": "K7c97c5df",
"禁用成员": "K1362a512",
"启用成员": "K6e1289b1",
"删除成员": "K1f4b5385",
"添加部门": "K26c698bb",
"添加子部门": "Kb9cf2a7d",
"重命名": "Kc83551f5",
"该数据删除后将无法找回,请确认是否删除?": "K5cfdd950",
"成员": "K74aef1ad",
"设置成员和对应的角色,成员只能够看到权限范围内的功能和数据。": "K3f1077c9",
"搜索部门": "Kdce62a6",
"数据源类型": "Ka46b9b24",
"数据源地址": "Kbb0cdcd0",
"Organization": "Kd9dfb884",
"鉴权 Token": "K3e770a75",
"密钥": "K8ef69ee2",
"上传密钥": "Kba3507d6",
"密钥文件的后缀名一般为 .key 的文件内容": "K93ac0f23",
"上传证书": "K7cdd1331",
"证书文件的后缀名一般为 .crt 或 .pem 的文件内容": "K6d91905d",
"添加证书": "Kd0f6ded7",
"修改证书": "Ke5732d60",
"证书管理": "K3ca07a70",
"通过为 API 服务配置和管理 SSL 证书,企业可以加密数据传输,防止敏感信息被窃取或篡改。": "Kdb927f83",
"集群": "Ke93d36ed",
"修改配置": "K877985b7",
"设置访问 API 的集群,让 API 在分布式环境中稳定运行,并且能够根据业务需求进行灵活扩展和优化。": "Kdf66a675",
"正常": "Ke039b9b5",
"异常": "K23a3bd72",
"管理地址": "Kf12b3034",
"服务地址": "K867e6faf",
"同步地址": "K2a49373f",
"集群地址": "K5878440c",
"下一步": "K5e9022f8",
"设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。": "Kdbafd6f9",
"统计图表": "K1358acf",
"地址(IP:端口)": "K62dabdf6",
"组织(Organization": "K2db12335",
"资源配置": "K8e7a0f80",
"设置角色的权限范围。": "K95c3fd8b",
"系统级别角色": "K138facd3",
"添加角色": "K6eac768d",
"团队级别角色": "Kb9c2cf02",
"单位:ms,最小值:1": "K2a16c93b",
"API 路由设置": "Ka945cfb1",
"API 基础信息": "K2e050340",
"拦截该接口的请求": "Kba92c499",
"开启拦截后,网关会拦截所有该路径的请求,相当于防火墙禁用了特定路径的访问。": "Kde9d6e8e",
"请求协议": "K6bc47edb",
"请求方式": "K1365fe45",
"转发规则设置": "K90f3c02f",
"拦截": "Kb7df6ac1",
"放行": "K5c1722fe",
"路由详情": "K28435c5c",
"只允许上传PNG、JPG或SVG格式的图片": "Ka9c08390",
"服务名称": "K413b9869",
"服务类型": "K9919285b",
"默认 AI 供应商": "Kcef64f4d",
"创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API": "K300c89d4",
"未配置任何 AI 模型供应商,": "Kcab588a9",
"立即配置": "Kb9b56111",
"API 调用前缀": "Kcf756b7a",
"作为服务内所有API的前缀,比如host/{service_name}/{api_path},一旦保存无法修改": "K13edc043",
"所属服务分类": "Kf52a584d",
"设置服务展示在服务市场中的哪个分类下": "K72b21be5",
"图标": "Kdc840242",
"仅支持 .png .jpg .jpeg .svg 格式的图片文件, 大于 1KB 的文件将被压缩": "K427a5bd5",
"Logo": "K44bc352d",
"删除服务": "Kde6bae17",
"删除操作不可恢复,请谨慎操作!": "K885ea699",
"上游": "Kda8d5ea1",
"服务提供了高性能 API 网关,并且可以无缝接入多种大型 AI 模型,并将这些 AI 能力打包成 API 进行调用,从而大幅简化了 AI 模型的使用门槛。同时,我们的平台提供了完善的 API 管理功能,支持 API 的创建、监控、访问控制等,保障开发者可以高效、安全地开发和管理 API 服务。": "K12f58863",
"添加服务": "K2d6658ed",
"输入名称、ID、所属团队、负责人查找服务": "K7b8f623f",
"上游类型": "Kad98e030",
"后端默认使用的IP地址": "Kdd9b5008",
"负载均衡": "Kc9acdb25",
"转发 Host": "K632dba5c",
"重写域名": "Kc1f08a63",
"超时时间": "K628f6851",
"超时重试次数": "Kaff62621",
"次": "Kf14d159b",
"调用频率限制": "Kc41ca30e",
"次/秒": "K753e8aeb",
"团队名称": "K813e1c0a",
"团队 ID": "K692f5aa6",
"团队 ID(team_id)可用于检索团队,一旦保存无法修改。": "K5de0bc2",
"团队负责人": "Ka63dd985",
"负责人对团队内的团队、服务、成员有管理权限": "Ka6bcd272",
"删除团队": "Ka2012bdd",
"服务数据清除后,方可删除": "Kbde1f3d",
"移除成员": "K395acc14",
"添加成员": "Kec46a57f",
"输入姓名查找": "K48724410",
"搜索用户名、邮箱": "Kb9052305",
"设置团队和成员,然后你可以在团队内创建服务和应用、订阅API,成员只能看到所属团队内的服务和应用。": "K5ece3bac",
"添加团队": "K510cdd27",
"输入名称、ID、负责人查找团队": "K9244ae14",
"配置团队": "Kc7b24b4b",
"旧密码": "Kecb51e2c",
"新密码": "K8266bcf2",
"确认密码": "Ka9aef039",
"两次密码不一致": "Kcf42dcda",
"修改密码": "Kf876a42d",
"管理个人账号": "K8ed884f",
"API调用统计": "K9be8e1d7",
"选择服务": "K521ab28e",
"选择API": "Kcc8265e1",
"路径": "Kc380335f",
"请输入请求路径进行搜索": "K8aefc1e4",
"重置": "K50d471b2",
"查询": "Kee8ae330",
"导出": "Ka2c794a2",
"退出全屏": "Kaf70c3b",
"(0)调用详情": "Kd22841a4",
"应用调用统计": "K1512e983",
"请选择应用": "Kb4d2007f",
"调用趋势": "K8c7f2d2e",
"(0)-(1)调用趋势": "K657c3452",
"调用量统计": "Kc04efb87",
"加入总体数据对比": "Keb98266e",
"(0)调用量": "K18c2ed46",
"(0)调用成功率": "Kc3741830",
"请求总数": "Ka6aa5863",
"请求成功率": "K9eaef42",
"转发总数": "K7082a4af",
"转发成功率": "K1ce386fb",
"状态码4xx数": "K87d6877e",
"状态码5xx数": "K4c8a54db",
"调用总体趋势": "Kd566283e",
"(0)报文量": "K21ad4a6a",
"请求报文量": "Kd23a0be6",
"响应报文量": "Kec3e8361",
"状态码4XX数": "Ke6250744",
"状态码5XX数": "K2d79d4e1",
"服务调用统计": "Kcf6553c6",
"请选择服务": "Kffcfe375",
"调用详情": "Ka65f739c",
"API 请求量 Top10": "K89b7ac79",
"应用调用量 Top10": "Kc0915603",
"服务被调用量 Top10": "Kf90b54",
"暂无请求统计数据": "Kfb26388",
"请求统计": "Kc8cbd8f8",
"暂无转发统计数据": "K8dece48",
"转发统计": "K1ee32434",
"暂无调用量统计数据": "Kcd125e4d",
"暂无报文量统计数据": "Kaa114e8b",
"报文量统计": "K3ad84406",
"集群配置并开启监控": "Kfa088d49",
"监控功能用于辅助管理集群内信息,请配置集群、设置监控信息后查看当前集群监控情况;": "K3da3b9a0",
"集群配置": "Kaddacfb",
"配置集群地址,以确保监控系统能够正确识别和连接到集群": "K4ac33975",
"配置集群信息": "Ke5ed9810",
"监控设置": "K1a132228",
"配置监控信息": "K6af08c3c",
"监控总览": "K4a1a14",
"服务被调用统计": "K69741ea7",
"API 调用统计": "K9c8d9933",
"亿": "K145e4941",
"万": "Ke6a935d",
"搜索分类或标签": "Kd59290a2",
"暂无API数据": "K6b75bdbc",
"搜索或选择应用": "Kd8a7a689",
"申请理由": "K4b15d6f5",
"应用管理": "Kb7e869a4",
"鉴权类型": "Kb71b5a13",
"Iss": "K4d1465ee",
"签名算法": "K5dcd7ed8",
"Secret": "K5b0eedd3",
"RSA 公钥": "K44f4ffe1",
"用户名 JsonPath": "Kc5ecd7d9",
"校验字段": "K417d85cf",
"是否 Base64 加密": "K3b82fe1d",
"AK": "K49b5f4a3",
"SK": "K31418470",
"Apikey": "Kbfeb5297",
"隐藏鉴权信息": "Ke64e43a",
"应用名称": "K5168eb63",
"应用 ID": "K546e46f",
"删除应用": "K95764d1d",
"鉴权详情": "K217cb125",
"添加鉴权": "K2bb63eca",
"编辑鉴权": "Kd74d69b7",
"修改": "K9cbe1e0",
"访问授权": "Kb6e9328f",
"添加授权": "Kd23d1716",
"永不过期": "K9dfa2c97",
"到期时间": "Kfa920c0",
"订阅的服务": "Kcce1af60",
"审批详情": "Kbeb4e991",
"取消订阅": "K3118fdb0",
"请确认是否取消订阅?": "Ked811bb1",
"取消订阅申请": "K50c39a62",
"请确认是否取消订阅申请?": "K1856c229",
"搜索服务": "K66ea2f0",
"审批中": "Kfeb2559b",
"添加应用": "K667bbbe7",
"暂无服务描述": "Ka4b45550",
"订阅的服务数量:已通过 (0) 个,申请中 (1) 个": "K3c7b175f",
"退出测试": "Kbe3e9335",
"服务市场": "K370a3eb2",
"服务详情": "Kf7ec36d",
"申请服务": "K58ca9485",
"介绍": "K59cdbec3",
"Basic URL": "K1b6777bb",
"申请": "K4aa9ed2c",
"服务信息": "K6c060779",
"接入应用": "K8723422e",
"供应方": "Kb97544cb",
"申请审批": "Kd55c6887",
"分类": "Kb32f0afe",
"版本": "K81634069",
"更新时间": "Keefda53d",
"无标签": "K96a2f1c8",
"API 数量": "K72b0c0b3",
"接入应用数量": "K93d5a66e",
"关联标签": "K96059c69",
"更新者": "K8b7c2592",
"添加 Open Api": "K32263abd",
"配置 Open Api": "K7829bb78",
"Open Api": "Kcdf76005",
"调用服务": "Ke2601944",
"放大": "K8504bca8",
"缩小": "K693c1b41"
}
@@ -0,0 +1,747 @@
{
"Kc0e5ef9f": "Workspace",
"K3863c722": "My",
"K4de11e23": "Home",
"Kfe93ef35": "Applications",
"Kb58e0c3f": "Services",
"Kc9e489f5": "Team",
"K61c89f5f": "API Portal",
"K16d71239": "Analytics",
"K714c192d": "Runtime",
"Kd57dfe97": "Topology",
"K3fe97dcc": "System Settings",
"K67ef3525": "Organization",
"K74aef1ad": "Members",
"Kf644225f": "Roles",
"K958da71f": "Category",
"Kf270ca55": "Operations & Integrations",
"Ke93d36ed": "Cluster",
"K9708a557": "Monitoring Reports",
"K481e8a05": "Certificate",
"Kca53edd0": "Logs",
"Kb283e720": "Resources",
"K631d646f": "Open API",
"K6535ff9c": "Account Settings",
"Kf15499b4": "Log Out",
"Kabbd6e6": "Docs",
"K1196b104": "APIPark - API Developer Portal",
"K1f42de3": "HTTP Status Codes",
"K4770dff4": "System Status Codes",
"Kf89e58f1": "Description",
"K9e53c664": "Submit",
"Kf8e7294c": "Previous Step",
"Ka0451c97": "Cancel",
"Kb1dedda3": "Close",
"Kb2fc7600": "Add Configuration",
"K4e07217d": "Edit Configuration",
"K4ea968fe": "Edit (0)",
"Ka7aaaeb": "Add (0)",
"Kaff78ecf": "Please Enter Key",
"K65d46535": "Please Enter Value",
"Kc14b2ea3": "Back",
"K11d3633a": "ID",
"Kbff43de3": "Name",
"K16ca79ef": "Driver",
"K7a369eef": "Published",
"Kcfa1a4d2": "Offline",
"K771dc3b7": "Online",
"K530f5951": "View",
"Kecbd7449": "Delete",
"K1cbe2507": "Confirm",
"K48325b6": "Search (0)",
"Ka1d885c1": "Add",
"Kad207008": "Edit",
"Ke4b7722": "简体中文",
"Kd185073d": "English",
"K1ff96ff": "Apply System",
"K9bf855d6": "Team",
"K11b994ed": "Applicant",
"K939baba7": "Application Date",
"Kdab2e63b": "Version Number",
"K8b29c460": "Version Description",
"K36a72ad1": "API List",
"K54e44357": "Upstream List",
"Kb8e8e6f5": "Remarks",
"K1ab0ae5b": "Application Reason",
"K53c00c3c": "Review Comments",
"K7edf331d": "Time",
"Kef45b208": "1 Hour",
"K9dbf22b8": "24 Hours",
"K820fbfab": "3 Days",
"Kd6d28fc": "7 Days",
"K3d7465f7": "File Logs",
"Kc87167a0": "HTTP Logs",
"K54630fe8": "Kafka Logs",
"Kd5c3966e": "NSQ Logs",
"K2e3de2c1": "Syslog Logs",
"Kb1cfa6e7": "Access Logs",
"K23fda291": "No Permission, Please Contact Administrator.",
"K4618cb0a": "WeChat Mini Program",
"Ka854f511": "Path Required for File Retrieval",
"Kaa11a695": "Non-HTTPS or Non-HTTP Code Samples Are Not Supported",
"Kbe46924e": "Search Programming Language...",
"Ke8e4f258": "Programming Language",
"K29c07a47": "Success Example",
"K1f5c814d": "Failure Example",
"K4ef022d7": "Default text/html;charset=UTF-8",
"Kd061b5bf": "No Sample Provided",
"Kc14cec33": "Binary",
"K48b4d9e3": "Request Header",
"Kcd347eaf": "Request Body",
"K9e100bfe": "Query Parameters",
"K3e9f12fd": "REST Parameters",
"K2bfa290c": "API Request Editor",
"Kb36d111a": "Response Header",
"K980bde79": "Response",
"Kb04d201a": "More Settings",
"Kee74f5b4": "Add Sub-Parameter",
"Kc7d3106c": "Add Row Below",
"Keaabd222": "Tag",
"K8ad2c50e": "Parameter Key",
"K67d68dd1": "Type",
"K29245f47": "Required",
"Ke32cbcd3": "Example",
"Kc13936c6": "Enter URL or cURL",
"Ka1ede006": "HTTP",
"K152ac44e": "Parameter Location",
"K1660ae72": "Match Type",
"K91ced765": "Parameter Value",
"K5b265628": "Operation Type",
"K1826982d": "Add or Modify",
"Kd65b55f5": "Match Parameter Value",
"K15f35bf2": "Forward Path",
"K79dec0dd": "Request Timeout",
"K7d465645": "Bind Upstream Service",
"K63a6404d": "Retry Time",
"K47740727": "Forward Header",
"K2b605d42": "More",
"K1df9fbd5": "Import",
"K5e85df18": "Import Format",
"K9eaf7885": "Full Replacement",
"Kf8c3a80b": "Insert at End",
"Kd96b2d7d": "Incremental Update",
"Kf2fc08eb": "Request Header",
"Ka45f1d8": "REST Parameters",
"K94bb113a": "Size",
"K359919b5": "Save As File",
"K38bf1b90": "Response",
"K59f4186e": "Response Header",
"K5f1e23fd": "Body",
"Kf404ef7d": "Send (Enter)",
"K2dbfd648": "Abort",
"Kacabc771": "Seconds",
"K13ae6a93": "Copy",
"Ke54a14a3": "Format",
"K43934f6d": "Search",
"K741decac": "Replace",
"Kd507abff": "Confirm",
"Kca2d1624": "(0) cannot be negative.",
"K792b255a": "(0) must be greater than or equal to (1).",
"Kf0bed26d": "Value Enumeration",
"K633a03ca": "Enumeration",
"Kd2766caf": "Minimum Length",
"Kd6d52485": "Maximum Length",
"Kea15f66c": "Minimum Value",
"K1af340ff": "Maximum Value",
"K68691e16": "Drag Files Here to Upload, or Click to Select Files",
"Kcec46ae": "Upload Files",
"K760fb044": "Files Selected",
"Kea2bdee0": "Please Enter Interface Name",
"K49053438": "Detailed Description",
"K148f6fa4": "Parameters Matching",
"K3ae4c789": "Forwarding Configuration",
"K2f4d0a37": "Request Parameters",
"Kde2d6dbd": "Return Example",
"K70e6069c": "Test API",
"Ke4603448": "Request Header",
"K89fd86b3": "Request Body",
"K8747e3c4": "Request Sample Code",
"K8613e6e7": "Response Example",
"Kab1c2159": "Response Header",
"Kd2be51d1": "Response Body",
"K831aa6c0": "Applicant - Application",
"K58ca9485": "Apply Service",
"K283f55b4": "Service System",
"Kd60d204": "Service Team",
"K3a9a3b75": "Approval Status",
"K4f57b2de": "Approver",
"K31dabc6b": "Approval Time",
"K8582af3f": "Reject",
"K54e27f57": "Approve",
"K7eeca185": "Approval Complete",
"Kd4061161": "Publishing Under Review",
"K823bfe63": "Online",
"K97ddb3f8": "-",
"Kc9315fa1": "Rejected",
"K3fbe7511": "Publishing Error",
"Ke64e695c": "Publishing",
"K17f93984": "API",
"K1365fe45": "Request Method",
"Kc380335f": "Path",
"K4ee62e8": "This API Lacks (0)(1)(2), Please Provide",
"K385591f3": "Forwarding Info,",
"K68415c14": "Document Info,",
"K133b75e9": "Upstream Info,",
"Kad98e030": "Upstream Type",
"Kdeed8399": "Static Upstream",
"K78b1ca25": "Address",
"K6208054": "Pending Approval",
"K1be7f021": "Approved",
"K677a4959": "Publishing Terminated",
"Kfd6d2d3d": "Closed",
"K9d7e880a": "Version",
"K855135f": "Create Time",
"Kcbf39b82": "Status",
"K339d15b5": "Creator",
"K7194cea2": "Review Time",
"K69827c60": "Publishing Status",
"K1644b775": "New",
"K4fdf4dcc": "Change",
"K33d66e26": "No Change",
"K9b70c007": "Missing Fields",
"Kd85208a3": "Rejected",
"K8adf7f8b": "Under Review",
"Kad6aa439": "Subscribed",
"K3118fdb0": "Unsubscribe",
"K9a68443b": "Cancel",
"K18307d56": "Manual Addition",
"K705fe9f5": "Subscription Application",
"Kbc96ebec": "Applicant Application",
"K1f89176d": "Team",
"Kfe731dfc": "Action",
"K71661ee8": "Required Field",
"Kcbee3f8": "Invalid Email Address",
"K442cfba1": "Please Enter",
"K3bb646e4": "Please Select",
"Ka4ecfa40": "English, Numbers, or Underscores Only; Must Start with a Letter",
"K39686a7f": "Supports Combination of Letters, Numbers, Hyphens, and Underscores",
"K4d6a0190": "Approval Comments Required When Rejecting",
"K37318b68": "Unable to Connect to Cluster; Please Check Address or Firewall Configuration",
"K7f0c746d": "Success",
"K6a365d01": "Failed, Please try again",
"K978062b6": "In Progress",
"Kca7bd6d4": "Loading Data",
"K3c93b77e": "Failed to Retrieve Data",
"Ke108c369": "Success",
"K9168d3e": "Redirecting to Login Page",
"K2f8a7ab7": "Review Comments Not Provided",
"Kb858d78a": "Copy Successful",
"K26e85d15": "Copy Failed, Please Copy Manually",
"K5cfdd950": "This Data Will Be Permanently Deleted, Are You Sure You Want to Delete?",
"K2a3f24ac": "Default Worksheet",
"K7e1ab4b0": "To",
"Kf1b166e7": "Details",
"K28555332": "URLs with Double Slashes // Are Not Supported",
"Keb9fcdad": "Username",
"Kc654b275": "Email",
"Kbe2ecc69": "Department",
"K759fb403": "Status",
"K52c8a730": "Enable",
"K718c9310": "Disable",
"K2c5882be": "Bind Domain",
"K1cc07937": "Expiration Date",
"K8b7c2592": "Updated By",
"Keefda53d": "Update Time",
"K9f3db3ca": "Cluster Name",
"Ke7487049": "Cluster ID",
"Kb660ffe8": "Node Name",
"Kf12b3034": "Management Address",
"K867e6faf": "Service Address",
"K37348a5e": "Cluster Sync Address",
"K2a49373f": "Cluster Sync Address",
"K151d2bb7": "Environment Name",
"Kfa744afd": "Clusters",
"K23a3bd72": "Error",
"Ke039b9b5": "Normal",
"K5c123bad": "Role",
"K76036e25": "HTTP Request Header",
"K8d4cbf50": "Cookie",
"K44607e3f": "Exact Match",
"Kc287500a": "Prefix Match",
"Kfc0b1147": "Suffix Match",
"Ka4a92043": "Substring Match",
"K30b2e44f": "Non-Exact Match",
"Kb1587991": "Null Match",
"K1e97dbd8": "Exists Match",
"Kc8ee3e62": "Does Not Exist Match",
"K87c5a801": "Case-Sensitive Regex Match",
"K95f062f1": "Case-Insensitive Regex Match",
"Kfbd230a5": "Any Match",
"K413b9869": "Service Name",
"K1e84ad04": "Service ID",
"K72b0c0b3": "APIs",
"Kf7200cd9": "Admin",
"Kefaf9956": "Create Time",
"K98db2cb9": "Application Status",
"Ke792d01c": "Service Association",
"K61b62ace": "Source",
"Ke63767cf": "Addition Time",
"K3a67ea90": "Subscriber",
"K442937c4": "Subscription Time",
"K34111022": "Protocol/Method",
"K62d10724": "URL",
"Ka9481f95": "Creator",
"Kf88d56fd": "Upstream ID",
"K11a92fb2": "Updater",
"K2c2bc64f": "Dynamic Service Discovery",
"Kc9a2a716": "HTTPS",
"Ka7f8266f": "Weighted Round-Robin",
"Kd17edabd": "IP Hash",
"Kaeba0229": "Pass Through Client Request Host",
"K6d7e2fd0": "Use Upstream Service Host",
"K31332633": "Rewrite Host",
"Ke65db976": "Weight",
"Kf966b12d": "Internal Service: Accessible via Gateway, but Not Listed in Service Plaza",
"Kfc939e49": "Public Service: Accessible via Gateway, Listed in Service Plaza, and Subscribable by Other Applications",
"Ke96ccf45": " ",
"K5582ac8": "Request Path",
"K92485dd1": "All APIs",
"Ke64e43a": "Hide Auth Info",
"K1a78e6f0": "Expiration Time",
"K40bbb0a3": "Service ID",
"K9919285": "Service Type",
"K63938137": "Public Service",
"Kfb20a12": "Internal Service",
"Kedd64e4d": "Disabled",
"Ka29b346f": "Address (IP Port or Domain)",
"K63b1e0dc": "Weight (0-999)",
"K74ab00a3": "Approved",
"Kea996156": "Publishing Application Record",
"Kbea7d266": "Associated System",
"Ka36c13cc": "Calling System Name",
"Kd78d73a7": "Calling Service Name",
"K73c144eb": "Current System Name",
"K285bd65e": "Called Service Name",
"K5cbab635": "Called System Name",
"K93c2696e": "Online Result",
"K43fcaf94": "Success",
"Kc71c6a9": "Online Failed",
"K56c686f8": "Failed",
"K3ba29a85": "API",
"Kda8d5ea1": "Upstream",
"Kdefa9caa": "Introduction",
"K36856e71": "Publish",
"K6382bbfd": "Subscription Management",
"K2eef4e4": "Subscription Review",
"Ka97bd9e5": "Subscription Management",
"K5974bf24": "Management",
"K3fa5c4c3": "Call Topology",
"Kb5c7b82d": "Settings",
"K2472615e": "Services",
"Kc02aa5f1": "APIs",
"Ke08ff808": "Addition Date",
"Kd7d84192": "Name",
"Kc88e03b6": "Team Roles",
"Kdf35c48c": "All Members",
"K3818f03d": "Approval",
"K56b4254f": "Publishing Application",
"Kd518ba3e": "Hello! Welcome to APIPark",
"Ke66e4182": "APIPark allows you to quickly build an API Developer Portal within your enterprise, offering extreme forwarding performance, API observability, service governance, multi-tenant management, subscription approval processes, and many other benefits.",
"Kedd41c18": "Welcome to Star ✨ our Github Repository or give us some feedback.",
"Kef02fd87": "Quick Start",
"K43a3b38d": "We've provided some tasks to help you quickly get acquainted with APIPark.",
"Kc8239422": "Teams include personnel, applications, and services. Data between different teams is isolated, and can be used to manage different departments/project teams/teams within the enterprise.",
"Kd5be0cd7": "Services include a set of APIs and can be published to the API Developer Portal for use by other teams.",
"K4ea67613": "Applications serve as identities for applying for services and calling APIs. They can apply for service calls in the API Developer Portal, and each application has its own independent API access Auth.",
"Ka4748416": "Search for Services and APIs",
"K383e17e5": "You can view all public services in the API Developer Portal.",
"K8f7808e6": "Subscribe to Services",
"Kb0755523": "To call an API of a particular service, you need to subscribe to the service first and wait for approval from the team providing the service before initiating API requests.",
"Kd28a1aa5": "Review Subscription Applications",
"K4472e361": "The team providing the service can review subscription applications from other teams. Only after approval can applications initiate API requests.",
"K297d8563": "The Dashboard provides various statistical charts to help us understand the API's operating status.",
"K48f7e21f": "Learn More Features",
"Ka3626c8c": "Hide Quick Start",
"Kd2c1a316": "Log In",
"Kf076f63c": "Please Enter Account",
"K80a560a1": "Account",
"K25c895d5": "Please Enter Password",
"K551b0348": "Password",
"K192b3e38": "Guest Mode",
"K91aa4801": "In guest mode, you can view all pages and features, but cannot edit data. Guest mode is for product feature exploration only.",
"K480045ce": "Version (0)-(1)",
"Kadee8e49": "Log Configuration",
"K33c76dbc": "Department",
"K84829ca9": "Parent Department ID",
"K4d7fc74b": "Sub-department",
"Ka16e6c44": "Inactive or Disabled Members Cannot Be Added to Departments",
"Ked03ba97": "Please Select the Department to Which the Member Should Be Added",
"K5e237e06": "Name",
"K184d3473": "Add Account",
"K1ecb35f2": "Edit Member",
"Ke6f00b44": "Join Department",
"K501cb1e7": "Are You Sure You Want to Delete This Member? This Action Cannot Be Undone.",
"Kf20863b5": "Members and Departments",
"K5f27a546": "Enter Username, Email to Search for Members",
"K26c698bb": "Add Department",
"Kb9cf2a7d": "Add Sub-department",
"Kc83551f5": "Rename",
"K3f1077c9": "Set Member Roles; Members Can Only See Features and Data Within Their Permission Scope.",
"Kdce62a6": "Search Department",
"K8ef69ee2": "Key",
"Kba3507d6": "Upload Key",
"K93ac0f23": "Key Files Usually Have a .key File Extension",
"K7cdd1331": "Upload Certificate",
"K6d91905d": "Certificate Files Usually Have a .crt or .pem File Extension",
"Kd0f6ded7": "Add Certificate",
"Ke5732d60": "Modify Certificate",
"K3ca07a70": "Certificate Management",
"Kdb927f83": "By Configuring and Managing SSL Certificates for API Services, Enterprises Can Encrypt Data Transmission to Prevent Sensitive info from Being Stolen or Tampered With.",
"K877985b7": "Modify Configuration",
"Kdf66a675": "Set the Cluster Accessing the API to Ensure Stable Operation in a Distributed Environment and Flexibly Scale and Optimize According to Business Needs.",
"Kaf074220": "Not Configured",
"K5878440c": "Cluster Address",
"K5e9022f8": "Next Step",
"Kdbafd6f9": "Set Data Sources for Monitoring Reports; Once Configured, You Can Get Detailed API Call Statistical Charts.",
"K1358acf": "Statistical Charts",
"K8fa58214": "Data Source",
"K62dabdf6": "Address (IP:Port)",
"K2db12335": "Organization",
"K8e7a0f80": "Resource Configuration",
"Kabfe9512": "Save",
"K95c3fd8b": "Set Role Permission Scope.",
"K138facd3": "System-Level Role",
"K6eac768d": "Add Role",
"Kb9c2cf02": "Team-Level Role",
"Kb4ceecea": "Add Subcategory",
"K67479e88": "Modify Category",
"K2bc75e2c": "Add Category",
"Kab4aab44": "Rename Category",
"K8e0e6977": "Set Service Categories to Facilitate Quick API Discovery by Team Members.",
"Ke595a20a": "Category",
"K9679728f": "Parent Category ID",
"K9b2d08fd": "Subcategory",
"Kf14e76e5": "Replica",
"K2e050340": "Request Settings",
"K90f3c02f": "Forwarding Settings",
"K6ea8d549": "Edit",
"Kff5c18ac": "Editor",
"K2eb99415": "Forwarding Rules",
"Ke93388fd": "Edit",
"K1b1ae3b0": "Copy API",
"K84aabfd4": "Add API",
"K6a662463": "Search Name, URL",
"K59bc6280": "Details",
"K2a16c93b": "Unit: ms, Minimum Value: 1",
"K469e475a": "Retry Count",
"Kd568e15c": "Publishing Result",
"K35f990b0": "View Details",
"Kdbc1f6cb": "Publishing Application",
"Kb6860a3f": "Rollback",
"Ka3494f4b": "Are You Sure You Want to Rollback?",
"Kb397a99f": "Revoke Application",
"K7d401c0f": "Are You Sure You Want to Revoke the Application?",
"Ke1b79b93": "Terminate Publishing",
"Ka2449180": "Are You Sure You Want to Terminate Publishing?",
"K2cb02f38": "New Version",
"Ka9c08390": "Only PNG, JPG, or SVG Format Images Are Allowed",
"Kcf756b7a": "API Call Prefix",
"K43d101a": "Optional, As Prefix for All APIs in Service, E.g., host/{service_name}/{api_path}, Cannot Be Modified Once Saved",
"Kdc840242": "Icon",
"K427a5bd5": "Only PNG, JPG, or SVG Format Images Are Allowed",
"K44bc352d": "Logo",
"Kf52a584d": "Service Category",
"K72b21be5": "Set the Category Where the Service Is Displayed in the Service Portal",
"Kde6bae17": "Delete Service",
"K885ea699": "This Action Is Irreversible, Please Proceed with Caution!",
"K617f34f1": "Updated By",
"K6ebca204": "Update Time",
"K39ab0358": "Add Subscriber",
"K2d6658ed": "Add Service",
"K7b8f623f": "Enter Name, ID, Associated Team, Person in Charge to Search Service",
"Kdd9b5008": "Default Backend IP Address",
"K6bc47edb": "Request Protocol",
"Kc9acdb25": "Load Balancing",
"K632dba5c": "Forward Host",
"Kc1f08a63": "Rewrite Domain Name",
"K628f6851": "Timeout",
"Kaff62621": "Timeout Retry Count",
"Kc41ca30e": "Rate Limiting",
"K813e1c0a": "Team Name",
"K692f5aa6": "Team ID",
"K5de0bc2": "Team ID (team_id) Can Be Used to Retrieve the Team, Cannot Be Modified Once Saved.",
"Ka63dd985": "Team Leader",
"Ka6bcd272": "Leader Has Management Authority over the Team, Services, and Members within the Team",
"Ka2012bdd": "Delete Team",
"Kbde1f3d": "Service Data Must Be Cleared Before Deletion",
"K395acc14": "Remove Member",
"Kec46a57f": "Add Member",
"K48724410": "Enter Name to Search",
"Kb9052305": "Search Username, Email",
"K5ece3bac": "Set Team and Members; You Can Then Create Services and Applications, Subscribe to APIs within the Team; Members Can Only See Services and Applications within Their Team.",
"K510cdd27": "Add Team",
"K9244ae14": "Enter Name, ID, Person in Charge to Search Team",
"Kc7b24b4b": "Configure Team",
"Kecb51e2c": "Old Password",
"K8266bcf2": "New Password",
"Ka9aef039": "Confirm Password",
"Kcf42dcda": "Passwords Do Not Match",
"Kf876a42d": "Change Password",
"K8ed884f": "Manage Personal Account",
"K9be8e1d7": "API Call Statistics",
"K521ab28e": "Select Service",
"Kcc8265e1": "Select API",
"K8aefc1e4": "Enter Request Path to Search",
"K50d471b2": "Reset",
"Kee8ae330": "Query",
"Ka2c794a2": "Export",
"Kaf70c3b": "Exit Fullscreen",
"Kd22841a4": "(0) Call Details",
"K1512e983": "Application Call Statistics",
"Kb4d2007f": "Please Select Application",
"K8c7f2d2e": "Call Trend",
"K657c3452": "(0)-(1) Call Trend",
"Kc04efb87": "Call Volume Statistics",
"Keb98266e": "Join Overall Data Comparison",
"K18c2ed46": "(0) Call Volume",
"Kc3741830": "(0) Success Rate",
"Ka6aa5863": "Total Requests",
"K9eaef42": "Success Rate",
"K7082a4af": "Total Forwards",
"K1ce386fb": "Forward Success Rate",
"K87d6877e": "4xx",
"K4c8a54db": "5xx",
"Kd566283e": "Overall Call Trend",
"Kd23a0be6": "Request Payload Volume",
"Kec3e8361": "Response Payload Volume",
"Ke6250744": "4XX",
"K2d79d4e1": "5XX",
"Kcf6553c6": "Service Call Statistics",
"Kffcfe375": "Please Select Service",
"Ka65f739c": "Call Details",
"K89b7ac79": "Top 10 API Requests",
"Kc0915603": "Top 10 Application Calls",
"Kf90b54": "Top 10 Service Calls",
"Kfb26388": "No Request Statistics Data",
"Kc8cbd8f8": "Request Statistics",
"K8dece48": "No Forwarding Statistics Data",
"K1ee32434": "Forwarding Statistics",
"Kcd125e4d": "No Call Volume Statistics Data",
"Kaa114e8b": "No Payload Volume Statistics Data",
"K3ad84406": "Payload Volume Statistics",
"K19a3ebe0": "Successful Requests",
"Kcaa8259": "Successful Forwards",
"K888f038f": "Failed Status Code Count",
"K42d2bef2": "Average Response Time (ms)",
"K9197c994": "Maximum Response Time (ms)",
"K7c2f3fee": "Minimum Response Time (ms)",
"K3d85ea54": "Average Request Traffic (KB)",
"Keec09d32": "Maximum Request Traffic (KB)",
"K3786b48": "Minimum Request Traffic (KB)",
"K5168eb63": "Application Name",
"K546e46f": "Application ID",
"K4a1a14": "Monitoring Overview",
"K69741ea7": "Service Call Statistics",
"K9c8d9933": "API Call Statistics",
"K28cf9613": "Per Minute",
"K18f25019": "Every 5 Minutes",
"Kf00f01ca": "Per Hour",
"Kfcda87fc": "Per Day",
"K29ec75dc": "Per Week",
"K145e4941": "B",
"Ke6a935d": "10K",
"K8f7abcab": " Times",
"K146477a8": "Service Tags",
"K4de0af74": "Service Categories",
"Kcce1af60": "Subscribed Services",
"Kb6e9328f": "Authorization",
"Kb7e869a4": "Settings",
"Kd59290a2": "Search Categories or Tags",
"K6b75bdbc": "No API Data",
"Kd8a7a689": "Search or Select Application",
"K4b15d6f5": "Application Reason",
"Kb71b5a13": "Auth Type",
"K4d1465ee": "ISS",
"K5dcd7ed8": "Signature Algorithm",
"K5b0eedd3": "Secret",
"K44f4ffe1": "RSA Public Key",
"Kc5ecd7d9": "Username JsonPath",
"K417d85cf": "Validation Field",
"K3b82fe1d": "Base64 Encryption",
"K49b5f4a3": "AK",
"K31418470": "SK",
"Kbfeb5297": "APIKey",
"K95764d1d": "Delete Application",
"K217cb125": "Auth Details",
"K2bb63eca": "Add Auth",
"Kd74d69b7": "Edit Auth",
"K9cbe1e0": "Modify",
"Kd23d1716": "Add Authorization",
"K9dfa2c97": "Never Expire",
"Kfa920c0": "Expiration Time",
"Kbeb4e991": "Review Details",
"Ked811bb1": "Do You Want to Unsubscribe?",
"K50c39a62": "Cancel",
"K1856c229": "Do You Want to Unsubscribe?",
"K66ea2f0": "Search Service",
"Kfeb2559b": "Under Review",
"Ka2b6d281": "API Document",
"K667bbbe7": "Add Application",
"Ka4b45550": "No Service Description",
"K3c7b175f": "Subscribed Services: (0) Approved, (1) Pending",
"K850b4b2d": "Status Code",
"Kbe3e9335": "Exit Test",
"K370a3eb2": "Service Portal",
"Kf7ec36d": "Service Details",
"K59cdbec3": "Documents",
"K4aa9ed2c": "Apply",
"K6c060779": "Service Info",
"K8723422e": "Application",
"Kb97544cb": "Supplier",
"Kb32f0afe": "Category",
"K81634069": "Version",
"K96a2f1c8": "No Tags",
"K93d5a66e": "Applications",
"K3e770a75": "Auth Token",
"K96059c69": "Associated Tags",
"K32263abd": "Add Open API",
"K7829bb78": "Configure Open API",
"Kcdf76005": "Open API",
"Ke2601944": "Invoke Service",
"K8504bca8": "Zoom In",
"K693c1b41": "Zoom Out",
"Kfd50704d": "No (0) permission, please contact the administrator.",
"K48322168": "Unassigned",
"K98f247f9": "System Admin",
"K9c8a571f": "Team Admin",
"K929b485b": "Operations Admin",
"K82cc5ec2": "Regular Member",
"Ke41d7451": "Read-Only Member",
"Kf99e8b66": "Service Admin",
"Kda8db57a": "Service Developer",
"K216a1ac7": "Application Developer",
"K27924db": "Application Admin",
"K8dc5c723": "Driver Name",
"K9919285b": "Service Type",
"Kf14d159b": "Times",
"K753e8aeb": "TPS",
"K21ad4a6a": "(0) Payload",
"Kda249fe8": "Failed Request",
"Kcf2df651": "Failed Forwarding",
"K7e6a859d": "Scope",
"K3a008b34": "Add Entry",
"Ke0599ef7": "Add Address",
"K48d3b5c4": "File Name",
"Kafde0d2a": "Storage Directory",
"Kfb2926ac": "Log Rotation Period",
"Kd96c2c69": "Unit: Days",
"Kc2b776fa": "Output Format",
"K7b7cdac2": "Format Configuration",
"K2f59807a": "Server Address",
"K540488a8": "NSQD Address List",
"K8bc33a11": "Auth Secret",
"K1cd3002f": "Network Protocol",
"Kdfaa32c8": "Log Level",
"Ka46b9b24": "Data Source Type",
"Kbb0cdcd0": "Data Source Address",
"Kd9dfb884": "Organization",
"Kc0408d9c": "Single line",
"Ke3db239d": "Hour",
"K3509a9f8": "Day",
"Kb3960e83": "Unpublished",
"K8bd1e18": "Pending",
"K225a6c43": "Unit: Seconds, Minimum Value: 1",
"Ka450909c": "Organization",
"K62933442": "View System Roles",
"Kd677d04a": "View Team Roles",
"Kd352fa1d": "API Portal",
"K39280ee": "Operations",
"K4bf109e8": "View All Applications",
"Keceae2": "View All Services",
"K7c866f28": "View All Teams",
"Kf9dcef3a": "Routes",
"K6134bbe8": "Add Route",
"Kad6d2797": "Search Routes by Name and URL",
"K28435c5c": "Route Details",
"Kfa088d49": "Configure Cluster and Enable Monitoring",
"K3da3b9a0": "Monitoring function is used to assist in managing cluster info. Please configure the cluster and set up monitoring info before viewing the current cluster monitoring status.",
"Kaddacfb": "Cluster Configuration",
"K4ac33975": "Configure cluster address to ensure monitoring system can correctly identify and connect to the cluster",
"Ke5ed9810": "Configure Cluster info",
"K1a132228": "Monitoring Settings",
"K6af08c3c": "Configure Monitoring info",
"K7e52ffa3": "Deployment Status",
"Kad1c674c": "Protocol",
"Kad01bc3e": "Method",
"Kca1dc104": "Open Editor",
"Kba92c499": "Intercept Requests to This Interface",
"Kb7df6ac1": "Block",
"K5c1722fe": "Allow",
"Ke00c858c": "Upload File",
"K6d9dd1f5": "Replace File",
"K71753476": "Allow Access",
"K597435c5": "Runtime",
"Kde9d6e8e": "When interception is enabled, the gateway will block all traffic of this API path.",
"K4758140d": "Routes",
"K7c97c5df": "Unassign Department",
"K1362a512": "Disable Member",
"K6e1289b1": "Enable Member",
"K1f4b5385": "Delete Member",
"Kf85b83a0": "Enter URL to Search Route",
"K62840d62": "REST Services",
"Kd2c34e2c": "AI Services",
"K4d5960c1": "AI Setting",
"Kc6340091": "Context",
"K74ecb1fa": "Query",
"K79f2e2f9": "Conversation History",
"K3a8912ee": "New variable",
"Kb291a19": "New tool",
"K27ece71d": "AI Model Invocation Defaults to Using Only Query Variables; Enter '{' to Add New Variables",
"K14700c7": "AI Model Provider",
"K1786a4c8": "Add AI Service",
"K66060758": "Name",
"K2bb86fb4": "Prompt",
"K13ffbe88": "Variable",
"K79c8cfaf": "Enter the Description for This Interface",
"K8a35059b": "Model Configuration",
"Kfede1c7c": "Model",
"Ke99513a0": "Parameters",
"K18dccc1a": "Synchronize Latest Model",
"Ke66a17dd": "Required",
"Kb3e34847": "Get API Key from (0)",
"Kd9a46c29": "Default",
"K66a7d24c": "Configured",
"K28b68036": "Invalid characters, only alphabetical characters are allowed",
"Kefa2a4cf": "AI Route Configuration",
"Kccbf1ee1": "Support mainstream AI models by turning LLMs and prompts into standard REST APIs, then publish them to the API Portal.",
"K7ac2be34": "AI Model Management",
"K2260837a": "After configuring the AI model, you can use the corresponding large model to create AI services",
"Kdac8ce7e": "Open OpenAPI YAML Editor",
"Ka945cfb1": "API Route Configuration",
"K51d1eb5d": "API Route",
"Ke2ad803": "REST services offer high-performance API gateways that provide quick access to existing HTTP APIs and publish them to the API Portal.",
"K6206e4ad": "Upload OpenAPI Document (.json/.yaml)",
"Kfba46e6d": "Replace OpenAPI Document (.json/.yaml)",
"Kf5da1284": "No Approval Required: Allows any application to call the service",
"Kc59ff06d": "Manual Approval: Only applications that pass manual approval are allowed to call the service",
"Kcab588a9": "No AI model provider configured, ",
"Kb9b56111": "Configure Now",
"Kbfe02d7f": "Permanent",
"K1e9c479e": "No",
"Kaddfcb6b": "Yes",
"K12f58863": "services offer high-performance API gateways that provide quick access to existing HTTP APIs and publish them to the API Portal.",
"K8ab0fc95": "Common Setting",
"K2724314b": "provides detailed API call logs to help enterprises monitor, analyze, and audit API operation statuses.",
"Kecbb0e45": "System",
"Ka358e23d": "General",
"K449058e9": "API Gateway",
"K99935e6f": "AI Model",
"K1deaa2dd": "User",
"K4057391a": "Integration",
"K408bfcf1": "Advanced Tutorial",
"K2cdbb773": "Core Features",
"K3378c50d": "Account and Roles",
"Kda5bb930": "Invite your team members to join APIPark to manage and call APIs together.",
"K62e89ee7": "Permission Management",
"K3596804a": "To call an API of a particular service, you need to subscribe to the service first and wait for approval from the team providing the service before initiating API requests.",
"K1c15bb2e": "The team providing the service can review subscription applications from other teams. Only after approval can applications initiate API requests.",
"K3453272": "APIPark provides detailed API call logs to help enterprises monitor, analyze, and audit API operation statuses.",
"K1afaf20e": "Learn how APIPark can better manage APIs and AI",
"Kcef64f4d": "Default AI Provider",
"K13edc043": "As a prefix for all APIs in the service, e.g., host/{service_name}/{api_path}, cannot be modified once saved",
"Ka7bb958f": "AI",
"Kcb81cc64": "REST",
"Kea2f9279": "API Request Base URL",
"K7fc496a1": "The API base URL is typically set to the external network access address of the API gateway or the domain name bound to the API gateway.",
"Kb66fec9d": "API Request"
}
@@ -0,0 +1 @@
{}
@@ -0,0 +1,22 @@
{
"K1c15bb2e": "提供服务的团队可以审批来自其他团队的订阅申请,审批通过后的应用才可发起 API 请求。",
"Kcf9f90b8": "模型供应商",
"K71671763": "快速接入 AI",
"Ka8a5ec5": "配置你的 AI 模型",
"K10d7e99f": "通过 APIPark 快速接入各种 AI 模型,使用统一的格式来调用API,并且可以随意切换模型。",
"Kc057704a": "创建 AI 服务和 API",
"K76bb4a09": "创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。",
"K71b2c70f": "创建调用 Token",
"K9bdd8403": "为了安全地调用 API,你需要创建一个应用以及Token。",
"Kc5738b6c": "调用",
"Kd6d7ca1f": "现在你可以通过 Token 来调用这些 API。",
"K86cf95f": "快速接入 REST API",
"K7a3a8417": "创建 REST 服务和 API",
"K4a84214e": "统计 API 调用情况",
"K698296e2": "隐藏该教程",
"K6a7fa303": "无需审批",
"Kd196e8a4": "需要审批",
"K1b6777bb": "Basic URL",
"Kd55c6887": "申请审批",
"K300c89d4": "创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API"
}
@@ -0,0 +1,580 @@
{
"Kc0e5ef9f": "工作空间",
"K4de11e23": "首页",
"Kfe93ef35": "应用",
"Kb58e0c3f": "服务",
"Kc9e489f5": "团队",
"K61c89f5f": "API 市场",
"K16d71239": "仪表盘",
"K714c192d": "运行视图",
"Kd57dfe97": "系统拓扑图",
"K3fe97dcc": "系统设置",
"Kecbb0e45": "系统",
"Ka358e23d": "常规",
"K449058e9": "API 网关",
"K99935e6f": "AI 模型",
"K1deaa2dd": "用户",
"K80a560a1": "账号",
"Kf644225f": "角色",
"K4057391a": "集成",
"K8fa58214": "数据源",
"K481e8a05": "证书",
"Kca53edd0": "日志",
"Kb283e720": "资源",
"K631d646f": "Open API",
"K6535ff9c": "账号设置",
"Kf15499b4": "退出登录",
"Kabbd6e6": "文档",
"K1196b104": "APIPark - 企业API数据开放平台",
"K1f42de3": "HTTP 状态码",
"K4770dff4": "系统状态码",
"Kf89e58f1": "描述",
"K9e53c664": "提交",
"Kf8e7294c": "上一步",
"Ka0451c97": "取消",
"Kb1dedda3": "关闭",
"Kb2fc7600": "添加配置",
"K4e07217d": "编辑配置",
"K4ea968fe": "编辑(0)",
"Ka7aaaeb": "添加(0)",
"Kaff78ecf": "请输入Key",
"K65d46535": "请输入Value",
"Kc14b2ea3": "返回",
"K11d3633a": "ID",
"Kbff43de3": "名称",
"K16ca79ef": "Driver",
"K7a369eef": "已发布",
"Kcfa1a4d2": "下线",
"K771dc3b7": "上线",
"K530f5951": "查看",
"Kecbd7449": "删除",
"K1cbe2507": "确认",
"K48325b6": "搜索(0)名称",
"Kc6340091": "上下文",
"K74ecb1fa": "查询内容",
"K79f2e2f9": "会话历史",
"K3a8912ee": "添加新变量",
"Kb291a19": "添加工具",
"K27ece71d": "AI 模型调用默认仅使用 Query 变量,可输入 “{” 增加新变量。",
"Kdeed8399": "静态上游",
"K4ee62e8": "该 API 缺失(0)(1)(2)请先补充",
"K385591f3": "转发信息,",
"K68415c14": "文档信息,",
"K133b75e9": "上游信息,",
"K43fcaf94": "成功",
"Kc71c6a9": "上线失败",
"K56c686f8": "失败",
"K1ff96ff": "申请系统",
"K9bf855d6": "所属团队",
"K11b994ed": "申请人",
"K939baba7": "申请时间",
"Kdab2e63b": "版本号",
"K8b29c460": "版本说明",
"K4758140d": "路由列表",
"K54e44357": "上游列表",
"Kb8e8e6f5": "备注",
"K7e52ffa3": "上线情况",
"K1ab0ae5b": "申请原因",
"K53c00c3c": "审核意见",
"Kfd50704d": "暂无(0)权限,请联系管理员分配。",
"K7edf331d": "时间",
"Kef45b208": "近1小时",
"K9dbf22b8": "近24小时",
"K820fbfab": "近3天",
"Kd6d28fc": "近7天",
"Ke00c858c": "上传文件",
"K6d9dd1f5": "替换文件",
"K71753476": "是否放行",
"K597435c5": "监控",
"Ke66a17dd": "必填",
"K28b68036": "字符非法,仅支持英文",
"K6206e4ad": "上传 OpenAPI 文档 (.json/.yaml)",
"Kfba46e6d": "替换 OpenAPI 文档 (.json/.yaml)",
"Kdac8ce7e": "打开 OpenAPI YAML 编辑器",
"Kf5da1284": "无需审批:允许任何应用调用该服务",
"Kc59ff06d": "人工审批:仅允许通过人工审批的应用调用该服务",
"Kbfe02d7f": "永久",
"K1e9c479e": "否",
"Kaddfcb6b": "是",
"K23fda291": "暂无操作权限,请联系管理员分配。",
"K4618cb0a": "微信小程序",
"Ka854f511": "获取文件,需填路径",
"Kaa11a695": "暂不支持生成非 HTTPS 或非 HTTP 协议的代码示例",
"Kbe46924e": "搜索编程语言...",
"Ke8e4f258": "编程语言",
"K29c07a47": "成功示例",
"K1f5c814d": "失败示例",
"K4ef022d7": "默认 text/html;charset=UTF-8",
"Kd061b5bf": "暂未填写示例",
"Kc14cec33": "Binary",
"K48b4d9e3": "请求头部",
"Kcd347eaf": "请求体",
"K9e100bfe": "Query 参数",
"K3e9f12fd": "REST 参数",
"K2bfa290c": "api request editor",
"Kb36d111a": "返回头部",
"K980bde79": "返回值",
"Kb04d201a": "更多设置",
"Kee74f5b4": "添加子参数",
"Kc7d3106c": "向下添加行",
"Keaabd222": "标签",
"K8ad2c50e": "参数名",
"K67d68dd1": "类型",
"K29245f47": "必需",
"Ke32cbcd3": "示例",
"Kc13936c6": "输入 URL 或 cURL",
"Ka1ede006": "HTTP",
"K152ac44e": "参数位置",
"K1660ae72": "匹配类型",
"K91ced765": "参数值",
"K5b265628": "操作类型",
"K1826982d": "新增或修改",
"Kd65b55f5": "匹配参数值",
"K15f35bf2": "转发上游路径",
"K79dec0dd": "请求超时时间",
"K7d465645": "绑定上游服务",
"K63a6404d": "重试时间",
"K47740727": "转发上游请求头",
"K2b605d42": "More",
"K1df9fbd5": "导入",
"K5e85df18": "导入格式",
"K9eaf7885": "全量替换",
"Kf8c3a80b": "在末端插入",
"Kd96b2d7d": "增量更新",
"Kf2fc08eb": "请求头",
"Ka45f1d8": "Rest 参数",
"K94bb113a": "大小",
"K359919b5": "另存为文件",
"K38bf1b90": "响应",
"K59f4186e": "响应头",
"K5f1e23fd": "正文",
"Kf404ef7d": "发送(Enter)",
"K2dbfd648": "中止",
"Kacabc771": "秒",
"K13ae6a93": "复制",
"Ke54a14a3": "格式化",
"K43934f6d": "搜索",
"K741decac": "替代",
"Kd507abff": "确定",
"Kca2d1624": "The (0) must not be negative.",
"K792b255a": "The (0) must be greater than or equal to the (1).",
"Kf0bed26d": "值枚举",
"K633a03ca": "枚举",
"Kd2766caf": "最小长度",
"Kd6d52485": "最大长度",
"Kea15f66c": "最小值",
"K1af340ff": "最大值",
"K68691e16": "将文件拖拽至此处上传,或点击选择文件上传",
"Kcec46ae": "Upload Files",
"K760fb044": "Files Selected",
"Kea2bdee0": "请填写接口名称",
"K49053438": "详细说明",
"K148f6fa4": "高级匹配",
"K3ae4c789": "转发配置",
"K2f4d0a37": "请求参数",
"Kde2d6dbd": "返回示例",
"K70e6069c": "测试 API",
"Ke4603448": "请求 Header",
"K89fd86b3": "请求 Body",
"K8747e3c4": "请求示例代码",
"K8613e6e7": "响应示例",
"Kab1c2159": "响应 Header",
"Kd2be51d1": "响应 Body",
"K2a3f24ac": "默认工作表",
"K7e1ab4b0": "至",
"Kf1b166e7": "详情",
"K28555332": "暂不支持带有双斜杠//的url",
"K71661ee8": "必填项",
"Kcbee3f8": "不是有效邮箱地址",
"K617f34f1": "最近一次更新者",
"K6ebca204": "最近一次更新时间",
"Kabfe9512": "保存",
"K51d1eb5d": "API 路由",
"Ka2b6d281": "API 文档",
"Kdefa9caa": "使用说明",
"K36856e71": "发布",
"K6382bbfd": "订阅管理",
"K2eef4e4": "订阅审批",
"Ka97bd9e5": "订阅方管理",
"K5974bf24": "管理",
"K3fa5c4c3": "调用拓扑图",
"Kb5c7b82d": "设置",
"K1e84ad04": "服务 ID",
"K39ab0358": "新增订阅方",
"K18307d56": "手动添加",
"K705fe9f5": "订阅申请",
"K3a67ea90": "订阅方",
"Kefa2a4cf": "AI 路由设置",
"K66060758": "路由名称",
"K5582ac8": "请求路径",
"K2bb86fb4": "提示词",
"K13ffbe88": "变量",
"K79c8cfaf": "输入这个接口的描述",
"K469e475a": "重试次数",
"K8a35059b": "模型配置",
"Kf9dcef3a": "路由",
"K6134bbe8": "添加路由",
"Kf85b83a0": "输入 URL 查找路由",
"Kcf9f90b8": "模型供应商",
"Kfede1c7c": "模型",
"Ke99513a0": "参数",
"K3818f03d": "审批",
"K54e27f57": "通过",
"K8582af3f": "拒绝",
"Kd568e15c": "发布结果",
"K35f990b0": "查看详情",
"Kdbc1f6cb": "申请发布",
"Kb6860a3f": "回滚",
"Ka3494f4b": "请确认是否回滚?",
"Kb397a99f": "撤销申请",
"K7d401c0f": "请确认是否撤销申请?",
"Ke1b79b93": "终止发布",
"Ka2449180": "请确认是否终止发布?",
"K2cb02f38": "新建版本",
"Kb3e34847": "从 (0) 获取 API KEY",
"K66a7d24c": "已配置",
"Kaf074220": "未配置",
"Kd9a46c29": "默认",
"K7ac2be34": "AI 模型管理",
"K2260837a": "配置好 AI 模型后,你可以使用对应的大模型来创建 AI 服务",
"K18dccc1a": "同步最新模型",
"K6208054": "待审批",
"K74ab00a3": "已审批",
"K56b4254f": "发布申请",
"Kea2f9279": "API 调用地址",
"K7fc496a1": "API base URL 一般设置为API 网关的外部网络访问地址,或者是API网关绑定的域名。",
"K8ab0fc95": "常规设置",
"Kb66fec9d": "API 请求设置",
"K4de0af74": "服务分类",
"Kb4ceecea": "添加子分类",
"K67479e88": "修改分类名称",
"K2bc75e2c": "添加分类",
"Kab4aab44": "重命名分类",
"Ke595a20a": "分类名称",
"K9679728f": "父分类 ID",
"K9b2d08fd": "子分类名称",
"K71671763": "快速接入 AI",
"Ka8a5ec5": "配置你的 AI 模型",
"K10d7e99f": "通过 APIPark 快速接入各种 AI 模型,使用统一的格式来调用API,并且可以随意切换模型。",
"Kc057704a": "创建 AI 服务和 API",
"K76bb4a09": "创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。",
"K71b2c70f": "创建调用 Token",
"K9bdd8403": "为了安全地调用 API,你需要创建一个应用以及Token。",
"Kc5738b6c": "调用",
"Kd6d7ca1f": "现在你可以通过 Token 来调用这些 API。",
"K86cf95f": "快速接入 REST API",
"K7a3a8417": "创建 REST 服务和 API",
"K4a84214e": "统计 API 调用情况",
"K297d8563": "仪表盘中提供了多种统计图表,帮助我们了解 API 的运行情况。",
"K2cdbb773": "核心功能",
"K3378c50d": "账号与角色",
"Kda5bb930": "邀请你的团队成员加入 APIPark,共同管理和调用 API。",
"Kc8239422": "团队中包含了人员、应用和服务,不同团队之间的应用和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。",
"Kd5be0cd7": "服务内包含一组 API,并且可以发布到 API 市场被其他团队使用。",
"K62e89ee7": "权限管理",
"K8f7808e6": "订阅服务",
"Kb0755523": "如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审批后才可发起 API 请求。",
"Kd28a1aa5": "审批订阅申请",
"K1c15bb2e": "提供服务的团队可以审批来自其他团队的订阅申请,审批通过后的应用才可发起 API 请求。",
"K3453272": "APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。",
"Kd518ba3e": "Hello!欢迎使用 APIPark",
"Ke66e4182": "你能通过 APIPark 快速在企业内部构建 API 开放门户/市场,享受极致的转发性能、API 可观测、服务治理、多租户管理、订阅审批流程等诸多好处。",
"Kedd41c18": "如果你喜欢我们的产品,欢迎给我们 Star 或提供产品反馈意见。",
"Kef02fd87": "快速入门",
"K43a3b38d": "我们提供了一些任务来帮你快速了解 APIPark",
"K408bfcf1": "进阶教程",
"K1afaf20e": "了解 APIPark 如何更好地管理 API 和 AI",
"K48f7e21f": "了解更多功能",
"K698296e2": "隐藏该教程",
"Kd2c1a316": "登录",
"Kf076f63c": "请输入账号",
"K25c895d5": "请输入密码",
"K551b0348": "密码",
"K192b3e38": "访客模式",
"K91aa4801": "您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。",
"K480045ce": "Version (0)-(1)",
"Kadee8e49": "日志配置",
"K2724314b": "提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。",
"K33c76dbc": "部门名称",
"K84829ca9": "父部门 ID",
"K4d7fc74b": "子部门名称",
"Keb9fcdad": "用户名",
"Kc654b275": "邮箱",
"Kbe2ecc69": "部门",
"Ka16e6c44": "未激活、已禁用的成员无法加入到部门",
"Ked03ba97": "请选择成员需要新加入的部门",
"K184d3473": "添加账号",
"K1ecb35f2": "编辑成员信息",
"Ke6f00b44": "加入部门",
"K501cb1e7": "确定删除成员?此操作无法恢复,确认操作?",
"Kf20863b5": "成员与部门",
"K52c8a730": "启用",
"K718c9310": "禁用",
"K5f27a546": "输入用户名、邮箱查找成员",
"K7c97c5df": "移出当前部门",
"K1362a512": "禁用成员",
"K6e1289b1": "启用成员",
"K1f4b5385": "删除成员",
"K26c698bb": "添加部门",
"Kb9cf2a7d": "添加子部门",
"Kc83551f5": "重命名",
"K5cfdd950": "该数据删除后将无法找回,请确认是否删除?",
"K74aef1ad": "成员",
"K3f1077c9": "设置成员和对应的角色,成员只能够看到权限范围内的功能和数据。",
"Kdce62a6": "搜索部门",
"Ka46b9b24": "数据源类型",
"Kbb0cdcd0": "数据源地址",
"Kd9dfb884": "Organization",
"K3e770a75": "鉴权 Token",
"K8ef69ee2": "密钥",
"Kba3507d6": "上传密钥",
"K93ac0f23": "密钥文件的后缀名一般为 .key 的文件内容",
"K7cdd1331": "上传证书",
"K6d91905d": "证书文件的后缀名一般为 .crt 或 .pem 的文件内容",
"Kd0f6ded7": "添加证书",
"Ke5732d60": "修改证书",
"K3ca07a70": "证书管理",
"Kdb927f83": "通过为 API 服务配置和管理 SSL 证书,企业可以加密数据传输,防止敏感信息被窃取或篡改。",
"Ke93d36ed": "集群",
"K877985b7": "修改配置",
"Kdf66a675": "设置访问 API 的集群,让 API 在分布式环境中稳定运行,并且能够根据业务需求进行灵活扩展和优化。",
"Ke039b9b5": "正常",
"K23a3bd72": "异常",
"Kf12b3034": "管理地址",
"K867e6faf": "服务地址",
"K2a49373f": "同步地址",
"K5878440c": "集群地址",
"K5e9022f8": "下一步",
"Kdbafd6f9": "设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。",
"K1358acf": "统计图表",
"K62dabdf6": "地址(IP:端口)",
"K2db12335": "组织(Organization",
"K8e7a0f80": "资源配置",
"K95c3fd8b": "设置角色的权限范围。",
"K138facd3": "系统级别角色",
"K6eac768d": "添加角色",
"Kb9c2cf02": "团队级别角色",
"K2a16c93b": "单位:ms,最小值:1",
"Ka945cfb1": "API 路由设置",
"K2e050340": "API 基础信息",
"Kba92c499": "拦截该接口的请求",
"Kde9d6e8e": "开启拦截后,网关会拦截所有该路径的请求,相当于防火墙禁用了特定路径的访问。",
"K6bc47edb": "请求协议",
"K1365fe45": "请求方式",
"K90f3c02f": "转发规则设置",
"Kb7df6ac1": "拦截",
"K5c1722fe": "放行",
"K28435c5c": "路由详情",
"Ka9c08390": "只允许上传PNG、JPG或SVG格式的图片",
"K413b9869": "服务名称",
"K9919285b": "服务类型",
"Kcef64f4d": "默认 AI 供应商",
"Kcab588a9": "未配置任何 AI 模型供应商,",
"Kb9b56111": "立即配置",
"Kcf756b7a": "API 调用前缀",
"K13edc043": "作为服务内所有API的前缀,比如host/{service_name}/{api_path},一旦保存无法修改",
"Kf52a584d": "所属服务分类",
"K72b21be5": "设置服务展示在服务市场中的哪个分类下",
"Kdc840242": "图标",
"K427a5bd5": "仅支持 .png .jpg .jpeg .svg 格式的图片文件, 大于 1KB 的文件将被压缩",
"K44bc352d": "Logo",
"Kde6bae17": "删除服务",
"K885ea699": "删除操作不可恢复,请谨慎操作!",
"Kda8d5ea1": "上游",
"K12f58863": "服务提供了高性能 API 网关,并且可以无缝接入多种大型 AI 模型,并将这些 AI 能力打包成 API 进行调用,从而大幅简化了 AI 模型的使用门槛。同时,我们的平台提供了完善的 API 管理功能,支持 API 的创建、监控、访问控制等,保障开发者可以高效、安全地开发和管理 API 服务。",
"K2d6658ed": "添加服务",
"K7b8f623f": "输入名称、ID、所属团队、负责人查找服务",
"Kad98e030": "上游类型",
"Kdd9b5008": "后端默认使用的IP地址",
"Kc9acdb25": "负载均衡",
"K632dba5c": "转发 Host",
"Kc1f08a63": "重写域名",
"K628f6851": "超时时间",
"Kaff62621": "超时重试次数",
"Kf14d159b": "次",
"Kc41ca30e": "调用频率限制",
"K753e8aeb": "次/秒",
"K813e1c0a": "团队名称",
"K692f5aa6": "团队 ID",
"K5de0bc2": "团队 ID(team_id)可用于检索团队,一旦保存无法修改。",
"Ka63dd985": "团队负责人",
"Ka6bcd272": "负责人对团队内的团队、服务、成员有管理权限",
"Ka2012bdd": "删除团队",
"Kbde1f3d": "服务数据清除后,方可删除",
"K395acc14": "移除成员",
"Kec46a57f": "添加成员",
"K48724410": "输入姓名查找",
"Kb9052305": "搜索用户名、邮箱",
"K5ece3bac": "设置团队和成员,然后你可以在团队内创建服务和应用、订阅API,成员只能看到所属团队内的服务和应用。",
"K510cdd27": "添加团队",
"K9244ae14": "输入名称、ID、负责人查找团队",
"Kc7b24b4b": "配置团队",
"Kecb51e2c": "旧密码",
"K8266bcf2": "新密码",
"Ka9aef039": "确认密码",
"Kcf42dcda": "两次密码不一致",
"Kf876a42d": "修改密码",
"K8ed884f": "管理个人账号",
"K9be8e1d7": "API调用统计",
"K521ab28e": "选择服务",
"Kcc8265e1": "选择API",
"Kc380335f": "路径",
"K8aefc1e4": "请输入请求路径进行搜索",
"K50d471b2": "重置",
"Kee8ae330": "查询",
"Ka2c794a2": "导出",
"Kaf70c3b": "退出全屏",
"Kd22841a4": "(0)调用详情",
"K1512e983": "应用调用统计",
"Kb4d2007f": "请选择应用",
"K8c7f2d2e": "调用趋势",
"K657c3452": "(0)-(1)调用趋势",
"Kc04efb87": "调用量统计",
"Keb98266e": "加入总体数据对比",
"K18c2ed46": "(0)调用量",
"Kc3741830": "(0)调用成功率",
"Ka6aa5863": "请求总数",
"K9eaef42": "请求成功率",
"K7082a4af": "转发总数",
"K1ce386fb": "转发成功率",
"K87d6877e": "状态码4xx数",
"K4c8a54db": "状态码5xx数",
"Kd566283e": "调用总体趋势",
"K21ad4a6a": "(0)报文量",
"Kd23a0be6": "请求报文量",
"Kec3e8361": "响应报文量",
"Ke6250744": "状态码4XX数",
"K2d79d4e1": "状态码5XX数",
"Kcf6553c6": "服务调用统计",
"Kffcfe375": "请选择服务",
"Ka65f739c": "调用详情",
"K89b7ac79": "API 请求量 Top10",
"Kc0915603": "应用调用量 Top10",
"Kf90b54": "服务被调用量 Top10",
"Kfb26388": "暂无请求统计数据",
"Kc8cbd8f8": "请求统计",
"K8dece48": "暂无转发统计数据",
"K1ee32434": "转发统计",
"Kcd125e4d": "暂无调用量统计数据",
"Kaa114e8b": "暂无报文量统计数据",
"K3ad84406": "报文量统计",
"Kfa088d49": "集群配置并开启监控",
"K3da3b9a0": "监控功能用于辅助管理集群内信息,请配置集群、设置监控信息后查看当前集群监控情况;",
"Kaddacfb": "集群配置",
"K4ac33975": "配置集群地址,以确保监控系统能够正确识别和连接到集群",
"Ke5ed9810": "配置集群信息",
"K1a132228": "监控设置",
"K6af08c3c": "配置监控信息",
"K4a1a14": "监控总览",
"K69741ea7": "服务被调用统计",
"K9c8d9933": "API 调用统计",
"K145e4941": "亿",
"Ke6a935d": "万",
"Kd59290a2": "搜索分类或标签",
"K6b75bdbc": "暂无API数据",
"Kd8a7a689": "搜索或选择应用",
"K4b15d6f5": "申请理由",
"Kb7e869a4": "应用管理",
"Kb71b5a13": "鉴权类型",
"K4d1465ee": "Iss",
"K5dcd7ed8": "签名算法",
"K5b0eedd3": "Secret",
"K44f4ffe1": "RSA 公钥",
"Kc5ecd7d9": "用户名 JsonPath",
"K417d85cf": "校验字段",
"K3b82fe1d": "是否 Base64 加密",
"K49b5f4a3": "AK",
"K31418470": "SK",
"Kbfeb5297": "Apikey",
"K1a78e6f0": "过期时间",
"Ke64e43a": "隐藏鉴权信息",
"K5168eb63": "应用名称",
"K546e46f": "应用 ID",
"K95764d1d": "删除应用",
"K217cb125": "鉴权详情",
"K2bb63eca": "添加鉴权",
"Kd74d69b7": "编辑鉴权",
"K9cbe1e0": "修改",
"Kb6e9328f": "访问授权",
"Kd23d1716": "添加授权",
"K9dfa2c97": "永不过期",
"Kfa920c0": "到期时间",
"Kcce1af60": "订阅的服务",
"Kbeb4e991": "审批详情",
"K3118fdb0": "取消订阅",
"Ked811bb1": "请确认是否取消订阅?",
"K50c39a62": "取消订阅申请",
"K1856c229": "请确认是否取消订阅申请?",
"K66ea2f0": "搜索服务",
"Kfeb2559b": "审批中",
"K667bbbe7": "添加应用",
"Ka4b45550": "暂无服务描述",
"K3c7b175f": "订阅的服务数量:已通过 (0) 个,申请中 (1) 个",
"Kbe3e9335": "退出测试",
"K370a3eb2": "服务市场",
"Kf7ec36d": "服务详情",
"K58ca9485": "申请服务",
"K59cdbec3": "介绍",
"K4aa9ed2c": "申请",
"K6c060779": "服务信息",
"K8723422e": "接入应用",
"Kb97544cb": "供应方",
"Kb32f0afe": "分类",
"K81634069": "版本",
"Keefda53d": "更新时间",
"K96a2f1c8": "无标签",
"K72b0c0b3": "API 数量",
"K93d5a66e": "接入应用数量",
"K96059c69": "关联标签",
"K8b7c2592": "更新者",
"K32263abd": "添加 Open Api",
"K7829bb78": "配置 Open Api",
"Kcdf76005": "Open Api",
"Ke2601944": "调用服务",
"K8504bca8": "放大",
"K693c1b41": "缩小",
"K3d7465f7": "文件日志",
"Kc87167a0": "HTTP日志",
"K54630fe8": "Kafka日志",
"Kd5c3966e": "NSQ日志",
"K2e3de2c1": "Syslog日志",
"K48322168": "未分配",
"K98f247f9": "超级管理员",
"K9c8a571f": "团队管理员",
"K929b485b": "运维管理员",
"K82cc5ec2": "普通成员",
"Ke41d7451": "只读成员",
"Kf99e8b66": "服务管理员",
"Kda8db57a": "服务开发者",
"K216a1ac7": "应用开发者",
"K27924db": "应用管理员",
"K8dc5c723": "驱动名称",
"Kda249fe8": "请求失败数",
"Kcf2df651": "转发失败数",
"K7e6a859d": "作用范围",
"K3a008b34": "添加条目",
"Ke0599ef7": "添加地址",
"K48d3b5c4": "文件名称",
"Kafde0d2a": "存放目录",
"Kfb2926ac": "日志分割周期",
"Kd96c2c69": "单位:天",
"Kc2b776fa": "输出格式",
"K7b7cdac2": "格式化配置",
"K2f59807a": "服务器地址",
"Kb1cfa6e7": "Access日志",
"K540488a8": "NSQD地址列表",
"K8bc33a11": "鉴权Secret",
"K1cd3002f": "网络协议",
"Kdfaa32c8": "日志等级",
"Kc0408d9c": "单行",
"Ke3db239d": "小时",
"K3509a9f8": "天",
"Kb3960e83": "未发布",
"K8bd1e18": "待发布",
"K225a6c43": "单位:s,最小值:1",
"K6a7fa303": "无需审批",
"Kd196e8a4": "需要审批",
"K1b6777bb": "Basic URL",
"Kd55c6887": "申请审批",
"K300c89d4": "创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API"
}
@@ -1,3 +1,104 @@
{
"K28b68036": "字符非法,仅支持英文"
}
"K1c15bb2e": "提供服务的团队可以审批来自其他团队的订阅申请,审批通过后的应用才可发起 API 请求。",
"Kc6340091": "上下文",
"K74ecb1fa": "查询内容",
"K79f2e2f9": "会话历史",
"K3a8912ee": "添加新变量",
"Kb291a19": "添加工具",
"K27ece71d": "AI 模型调用默认仅使用 Query 变量,可输入 “{” 增加新变量。",
"K7e52ffa3": "上线情况",
"Ke00c858c": "上传文件",
"K6d9dd1f5": "替换文件",
"Ke66a17dd": "必填",
"K28b68036": "字符非法,仅支持英文",
"K6206e4ad": "上传 OpenAPI 文档 (.json/.yaml)",
"Kfba46e6d": "替换 OpenAPI 文档 (.json/.yaml)",
"Kdac8ce7e": "打开 OpenAPI YAML 编辑器",
"Kf5da1284": "无需审批:允许任何应用调用该服务",
"Kc59ff06d": "人工审批:仅允许通过人工审批的应用调用该服务",
"Kbfe02d7f": "永久",
"K1e9c479e": "否",
"Kaddfcb6b": "是",
"K51d1eb5d": "API 路由",
"Kefa2a4cf": "AI 路由设置",
"K66060758": "路由名称",
"K2bb86fb4": "提示词",
"K13ffbe88": "变量",
"K79c8cfaf": "输入这个接口的描述",
"K8a35059b": "模型配置",
"Kf9dcef3a": "路由",
"K6134bbe8": "添加路由",
"Kf85b83a0": "输入 URL 查找路由",
"Kcf9f90b8": "模型供应商",
"Kfede1c7c": "模型",
"Ke99513a0": "参数",
"Kb3e34847": "从 (0) 获取 API KEY",
"K66a7d24c": "已配置",
"Kd9a46c29": "默认",
"K7ac2be34": "AI 模型管理",
"K2260837a": "配置好 AI 模型后,你可以使用对应的大模型来创建 AI 服务",
"K18dccc1a": "同步最新模型",
"Kea2f9279": "API 调用地址",
"K7fc496a1": "API base URL 一般设置为API 网关的外部网络访问地址,或者是API网关绑定的域名。",
"Kb66fec9d": "API 请求设置",
"K71671763": "快速接入 AI",
"Ka8a5ec5": "配置你的 AI 模型",
"K10d7e99f": "通过 APIPark 快速接入各种 AI 模型,使用统一的格式来调用API,并且可以随意切换模型。",
"Kc057704a": "创建 AI 服务和 API",
"K76bb4a09": "创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。",
"K71b2c70f": "创建调用 Token",
"K9bdd8403": "为了安全地调用 API,你需要创建一个应用以及Token。",
"Kc5738b6c": "调用",
"Kd6d7ca1f": "现在你可以通过 Token 来调用这些 API。",
"K86cf95f": "快速接入 REST API",
"K7a3a8417": "创建 REST 服务和 API",
"K4a84214e": "统计 API 调用情况",
"K698296e2": "隐藏该教程",
"K7c97c5df": "移出当前部门",
"K1362a512": "禁用成员",
"K6e1289b1": "启用成员",
"K1f4b5385": "删除成员",
"Ka46b9b24": "数据源类型",
"Kbb0cdcd0": "数据源地址",
"Kd9dfb884": "Organization",
"K2a49373f": "同步地址",
"Ka945cfb1": "API 路由设置",
"Kba92c499": "拦截该接口的请求",
"Kb7df6ac1": "拦截",
"K5c1722fe": "放行",
"K28435c5c": "路由详情",
"Kcab588a9": "未配置任何 AI 模型供应商,",
"Kb9b56111": "立即配置",
"Kfa088d49": "集群配置并开启监控",
"K3da3b9a0": "监控功能用于辅助管理集群内信息,请配置集群、设置监控信息后查看当前集群监控情况;",
"Kaddacfb": "集群配置",
"K4ac33975": "配置集群地址,以确保监控系统能够正确识别和连接到集群",
"Ke5ed9810": "配置集群信息",
"K1a132228": "监控设置",
"K6af08c3c": "配置监控信息",
"K54630fe8": "Kafka日志",
"Kd5c3966e": "NSQ日志",
"K2e3de2c1": "Syslog日志",
"Kda249fe8": "请求失败数",
"Kcf2df651": "转发失败数",
"K7e6a859d": "作用范围",
"K3a008b34": "添加条目",
"Ke0599ef7": "添加地址",
"K48d3b5c4": "文件名称",
"Kafde0d2a": "存放目录",
"Kfb2926ac": "日志分割周期",
"Kd96c2c69": "单位:天",
"Kc2b776fa": "输出格式",
"K7b7cdac2": "格式化配置",
"K2f59807a": "服务器地址",
"Kb1cfa6e7": "Access日志",
"K540488a8": "NSQD地址列表",
"K8bc33a11": "鉴权Secret",
"K1cd3002f": "网络协议",
"Kdfaa32c8": "日志等级",
"K6a7fa303": "无需审批",
"Kd196e8a4": "需要审批",
"K1b6777bb": "Basic URL",
"Kd55c6887": "申请审批",
"K300c89d4": "创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API"
}
@@ -0,0 +1,580 @@
{
"Kc0e5ef9f": "工作空间",
"K4de11e23": "首页",
"Kfe93ef35": "应用",
"Kb58e0c3f": "服务",
"Kc9e489f5": "团队",
"K61c89f5f": "API 市场",
"K16d71239": "仪表盘",
"K714c192d": "运行视图",
"Kd57dfe97": "系统拓扑图",
"K3fe97dcc": "系统设置",
"Kecbb0e45": "系统",
"Ka358e23d": "常规",
"K449058e9": "API 网关",
"K99935e6f": "AI 模型",
"K1deaa2dd": "用户",
"K80a560a1": "账号",
"Kf644225f": "角色",
"K4057391a": "集成",
"K8fa58214": "数据源",
"K481e8a05": "证书",
"Kca53edd0": "日志",
"Kb283e720": "资源",
"K631d646f": "Open API",
"K6535ff9c": "账号设置",
"Kf15499b4": "退出登录",
"Kabbd6e6": "文档",
"K1196b104": "APIPark - 企业API数据开放平台",
"K1f42de3": "HTTP 状态码",
"K4770dff4": "系统状态码",
"Kf89e58f1": "描述",
"K9e53c664": "提交",
"Kf8e7294c": "上一步",
"Ka0451c97": "取消",
"Kb1dedda3": "关闭",
"Kb2fc7600": "添加配置",
"K4e07217d": "编辑配置",
"K4ea968fe": "编辑(0)",
"Ka7aaaeb": "添加(0)",
"Kaff78ecf": "请输入Key",
"K65d46535": "请输入Value",
"Kc14b2ea3": "返回",
"K11d3633a": "ID",
"Kbff43de3": "名称",
"K16ca79ef": "Driver",
"K7a369eef": "已发布",
"Kcfa1a4d2": "下线",
"K771dc3b7": "上线",
"K530f5951": "查看",
"Kecbd7449": "删除",
"K1cbe2507": "确认",
"K48325b6": "搜索(0)名称",
"Kc6340091": "上下文",
"K74ecb1fa": "查询内容",
"K79f2e2f9": "会话历史",
"K3a8912ee": "添加新变量",
"Kb291a19": "添加工具",
"K27ece71d": "AI 模型调用默认仅使用 Query 变量,可输入 “{” 增加新变量。",
"Kdeed8399": "静态上游",
"K4ee62e8": "该 API 缺失(0)(1)(2)请先补充",
"K385591f3": "转发信息,",
"K68415c14": "文档信息,",
"K133b75e9": "上游信息,",
"K43fcaf94": "成功",
"Kc71c6a9": "上线失败",
"K56c686f8": "失败",
"K1ff96ff": "申请系统",
"K9bf855d6": "所属团队",
"K11b994ed": "申请人",
"K939baba7": "申请时间",
"Kdab2e63b": "版本号",
"K8b29c460": "版本说明",
"K4758140d": "路由列表",
"K54e44357": "上游列表",
"Kb8e8e6f5": "备注",
"K7e52ffa3": "上线情况",
"K1ab0ae5b": "申请原因",
"K53c00c3c": "审核意见",
"Kfd50704d": "暂无(0)权限,请联系管理员分配。",
"K7edf331d": "时间",
"Kef45b208": "近1小时",
"K9dbf22b8": "近24小时",
"K820fbfab": "近3天",
"Kd6d28fc": "近7天",
"Ke00c858c": "上传文件",
"K6d9dd1f5": "替换文件",
"K71753476": "是否放行",
"K597435c5": "监控",
"Ke66a17dd": "必填",
"K28b68036": "字符非法,仅支持英文",
"K6206e4ad": "上传 OpenAPI 文档 (.json/.yaml)",
"Kfba46e6d": "替换 OpenAPI 文档 (.json/.yaml)",
"Kdac8ce7e": "打开 OpenAPI YAML 编辑器",
"Kf5da1284": "无需审批:允许任何应用调用该服务",
"Kc59ff06d": "人工审批:仅允许通过人工审批的应用调用该服务",
"Kbfe02d7f": "永久",
"K1e9c479e": "否",
"Kaddfcb6b": "是",
"K23fda291": "暂无操作权限,请联系管理员分配。",
"K4618cb0a": "微信小程序",
"Ka854f511": "获取文件,需填路径",
"Kaa11a695": "暂不支持生成非 HTTPS 或非 HTTP 协议的代码示例",
"Kbe46924e": "搜索编程语言...",
"Ke8e4f258": "编程语言",
"K29c07a47": "成功示例",
"K1f5c814d": "失败示例",
"K4ef022d7": "默认 text/html;charset=UTF-8",
"Kd061b5bf": "暂未填写示例",
"Kc14cec33": "Binary",
"K48b4d9e3": "请求头部",
"Kcd347eaf": "请求体",
"K9e100bfe": "Query 参数",
"K3e9f12fd": "REST 参数",
"K2bfa290c": "api request editor",
"Kb36d111a": "返回头部",
"K980bde79": "返回值",
"Kb04d201a": "更多设置",
"Kee74f5b4": "添加子参数",
"Kc7d3106c": "向下添加行",
"Keaabd222": "标签",
"K8ad2c50e": "参数名",
"K67d68dd1": "类型",
"K29245f47": "必需",
"Ke32cbcd3": "示例",
"Kc13936c6": "输入 URL 或 cURL",
"Ka1ede006": "HTTP",
"K152ac44e": "参数位置",
"K1660ae72": "匹配类型",
"K91ced765": "参数值",
"K5b265628": "操作类型",
"K1826982d": "新增或修改",
"Kd65b55f5": "匹配参数值",
"K15f35bf2": "转发上游路径",
"K79dec0dd": "请求超时时间",
"K7d465645": "绑定上游服务",
"K63a6404d": "重试时间",
"K47740727": "转发上游请求头",
"K2b605d42": "More",
"K1df9fbd5": "导入",
"K5e85df18": "导入格式",
"K9eaf7885": "全量替换",
"Kf8c3a80b": "在末端插入",
"Kd96b2d7d": "增量更新",
"Kf2fc08eb": "请求头",
"Ka45f1d8": "Rest 参数",
"K94bb113a": "大小",
"K359919b5": "另存为文件",
"K38bf1b90": "响应",
"K59f4186e": "响应头",
"K5f1e23fd": "正文",
"Kf404ef7d": "发送(Enter)",
"K2dbfd648": "中止",
"Kacabc771": "秒",
"K13ae6a93": "复制",
"Ke54a14a3": "格式化",
"K43934f6d": "搜索",
"K741decac": "替代",
"Kd507abff": "确定",
"Kca2d1624": "The (0) must not be negative.",
"K792b255a": "The (0) must be greater than or equal to the (1).",
"Kf0bed26d": "值枚举",
"K633a03ca": "枚举",
"Kd2766caf": "最小长度",
"Kd6d52485": "最大长度",
"Kea15f66c": "最小值",
"K1af340ff": "最大值",
"K68691e16": "将文件拖拽至此处上传,或点击选择文件上传",
"Kcec46ae": "Upload Files",
"K760fb044": "Files Selected",
"Kea2bdee0": "请填写接口名称",
"K49053438": "详细说明",
"K148f6fa4": "高级匹配",
"K3ae4c789": "转发配置",
"K2f4d0a37": "请求参数",
"Kde2d6dbd": "返回示例",
"K70e6069c": "测试 API",
"Ke4603448": "请求 Header",
"K89fd86b3": "请求 Body",
"K8747e3c4": "请求示例代码",
"K8613e6e7": "响应示例",
"Kab1c2159": "响应 Header",
"Kd2be51d1": "响应 Body",
"K2a3f24ac": "默认工作表",
"K7e1ab4b0": "至",
"Kf1b166e7": "详情",
"K28555332": "暂不支持带有双斜杠//的url",
"K71661ee8": "必填项",
"Kcbee3f8": "不是有效邮箱地址",
"K617f34f1": "最近一次更新者",
"K6ebca204": "最近一次更新时间",
"Kabfe9512": "保存",
"K51d1eb5d": "API 路由",
"Ka2b6d281": "API 文档",
"Kdefa9caa": "使用说明",
"K36856e71": "发布",
"K6382bbfd": "订阅管理",
"K2eef4e4": "订阅审批",
"Ka97bd9e5": "订阅方管理",
"K5974bf24": "管理",
"K3fa5c4c3": "调用拓扑图",
"Kb5c7b82d": "设置",
"K1e84ad04": "服务 ID",
"K39ab0358": "新增订阅方",
"K18307d56": "手动添加",
"K705fe9f5": "订阅申请",
"K3a67ea90": "订阅方",
"Kefa2a4cf": "AI 路由设置",
"K66060758": "路由名称",
"K5582ac8": "请求路径",
"K2bb86fb4": "提示词",
"K13ffbe88": "变量",
"K79c8cfaf": "输入这个接口的描述",
"K469e475a": "重试次数",
"K8a35059b": "模型配置",
"Kf9dcef3a": "路由",
"K6134bbe8": "添加路由",
"Kf85b83a0": "输入 URL 查找路由",
"Kcf9f90b8": "模型供应商",
"Kfede1c7c": "模型",
"Ke99513a0": "参数",
"K3818f03d": "审批",
"K54e27f57": "通过",
"K8582af3f": "拒绝",
"Kd568e15c": "发布结果",
"K35f990b0": "查看详情",
"Kdbc1f6cb": "申请发布",
"Kb6860a3f": "回滚",
"Ka3494f4b": "请确认是否回滚?",
"Kb397a99f": "撤销申请",
"K7d401c0f": "请确认是否撤销申请?",
"Ke1b79b93": "终止发布",
"Ka2449180": "请确认是否终止发布?",
"K2cb02f38": "新建版本",
"Kb3e34847": "从 (0) 获取 API KEY",
"K66a7d24c": "已配置",
"Kaf074220": "未配置",
"Kd9a46c29": "默认",
"K7ac2be34": "AI 模型管理",
"K2260837a": "配置好 AI 模型后,你可以使用对应的大模型来创建 AI 服务",
"K18dccc1a": "同步最新模型",
"K6208054": "待审批",
"K74ab00a3": "已审批",
"K56b4254f": "发布申请",
"Kea2f9279": "API 调用地址",
"K7fc496a1": "API base URL 一般设置为API 网关的外部网络访问地址,或者是API网关绑定的域名。",
"K8ab0fc95": "常规设置",
"Kb66fec9d": "API 请求设置",
"K4de0af74": "服务分类",
"Kb4ceecea": "添加子分类",
"K67479e88": "修改分类名称",
"K2bc75e2c": "添加分类",
"Kab4aab44": "重命名分类",
"Ke595a20a": "分类名称",
"K9679728f": "父分类 ID",
"K9b2d08fd": "子分类名称",
"K71671763": "快速接入 AI",
"Ka8a5ec5": "配置你的 AI 模型",
"K10d7e99f": "通过 APIPark 快速接入各种 AI 模型,使用统一的格式来调用API,并且可以随意切换模型。",
"Kc057704a": "创建 AI 服务和 API",
"K76bb4a09": "创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。",
"K71b2c70f": "创建调用 Token",
"K9bdd8403": "为了安全地调用 API,你需要创建一个应用以及Token。",
"Kc5738b6c": "调用",
"Kd6d7ca1f": "现在你可以通过 Token 来调用这些 API。",
"K86cf95f": "快速接入 REST API",
"K7a3a8417": "创建 REST 服务和 API",
"K4a84214e": "统计 API 调用情况",
"K297d8563": "仪表盘中提供了多种统计图表,帮助我们了解 API 的运行情况。",
"K2cdbb773": "核心功能",
"K3378c50d": "账号与角色",
"Kda5bb930": "邀请你的团队成员加入 APIPark,共同管理和调用 API。",
"Kc8239422": "团队中包含了人员、应用和服务,不同团队之间的应用和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。",
"Kd5be0cd7": "服务内包含一组 API,并且可以发布到 API 市场被其他团队使用。",
"K62e89ee7": "权限管理",
"K8f7808e6": "订阅服务",
"Kb0755523": "如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审批后才可发起 API 请求。",
"Kd28a1aa5": "审批订阅申请",
"K1c15bb2e": "提供服务的团队可以审批来自其他团队的订阅申请,审批通过后的应用才可发起 API 请求。",
"K3453272": "APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。",
"Kd518ba3e": "Hello!欢迎使用 APIPark",
"Ke66e4182": "你能通过 APIPark 快速在企业内部构建 API 开放门户/市场,享受极致的转发性能、API 可观测、服务治理、多租户管理、订阅审批流程等诸多好处。",
"Kedd41c18": "如果你喜欢我们的产品,欢迎给我们 Star 或提供产品反馈意见。",
"Kef02fd87": "快速入门",
"K43a3b38d": "我们提供了一些任务来帮你快速了解 APIPark",
"K408bfcf1": "进阶教程",
"K1afaf20e": "了解 APIPark 如何更好地管理 API 和 AI",
"K48f7e21f": "了解更多功能",
"K698296e2": "隐藏该教程",
"Kd2c1a316": "登录",
"Kf076f63c": "请输入账号",
"K25c895d5": "请输入密码",
"K551b0348": "密码",
"K192b3e38": "访客模式",
"K91aa4801": "您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。",
"K480045ce": "Version (0)-(1)",
"Kadee8e49": "日志配置",
"K2724314b": "提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。",
"K33c76dbc": "部门名称",
"K84829ca9": "父部门 ID",
"K4d7fc74b": "子部门名称",
"Keb9fcdad": "用户名",
"Kc654b275": "邮箱",
"Kbe2ecc69": "部门",
"Ka16e6c44": "未激活、已禁用的成员无法加入到部门",
"Ked03ba97": "请选择成员需要新加入的部门",
"K184d3473": "添加账号",
"K1ecb35f2": "编辑成员信息",
"Ke6f00b44": "加入部门",
"K501cb1e7": "确定删除成员?此操作无法恢复,确认操作?",
"Kf20863b5": "成员与部门",
"K52c8a730": "启用",
"K718c9310": "禁用",
"K5f27a546": "输入用户名、邮箱查找成员",
"K7c97c5df": "移出当前部门",
"K1362a512": "禁用成员",
"K6e1289b1": "启用成员",
"K1f4b5385": "删除成员",
"K26c698bb": "添加部门",
"Kb9cf2a7d": "添加子部门",
"Kc83551f5": "重命名",
"K5cfdd950": "该数据删除后将无法找回,请确认是否删除?",
"K74aef1ad": "成员",
"K3f1077c9": "设置成员和对应的角色,成员只能够看到权限范围内的功能和数据。",
"Kdce62a6": "搜索部门",
"Ka46b9b24": "数据源类型",
"Kbb0cdcd0": "数据源地址",
"Kd9dfb884": "Organization",
"K3e770a75": "鉴权 Token",
"K8ef69ee2": "密钥",
"Kba3507d6": "上传密钥",
"K93ac0f23": "密钥文件的后缀名一般为 .key 的文件内容",
"K7cdd1331": "上传证书",
"K6d91905d": "证书文件的后缀名一般为 .crt 或 .pem 的文件内容",
"Kd0f6ded7": "添加证书",
"Ke5732d60": "修改证书",
"K3ca07a70": "证书管理",
"Kdb927f83": "通过为 API 服务配置和管理 SSL 证书,企业可以加密数据传输,防止敏感信息被窃取或篡改。",
"Ke93d36ed": "集群",
"K877985b7": "修改配置",
"Kdf66a675": "设置访问 API 的集群,让 API 在分布式环境中稳定运行,并且能够根据业务需求进行灵活扩展和优化。",
"Ke039b9b5": "正常",
"K23a3bd72": "异常",
"Kf12b3034": "管理地址",
"K867e6faf": "服务地址",
"K2a49373f": "同步地址",
"K5878440c": "集群地址",
"K5e9022f8": "下一步",
"Kdbafd6f9": "设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。",
"K1358acf": "统计图表",
"K62dabdf6": "地址(IP:端口)",
"K2db12335": "组织(Organization",
"K8e7a0f80": "资源配置",
"K95c3fd8b": "设置角色的权限范围。",
"K138facd3": "系统级别角色",
"K6eac768d": "添加角色",
"Kb9c2cf02": "团队级别角色",
"K2a16c93b": "单位:ms,最小值:1",
"Ka945cfb1": "API 路由设置",
"K2e050340": "API 基础信息",
"Kba92c499": "拦截该接口的请求",
"Kde9d6e8e": "开启拦截后,网关会拦截所有该路径的请求,相当于防火墙禁用了特定路径的访问。",
"K6bc47edb": "请求协议",
"K1365fe45": "请求方式",
"K90f3c02f": "转发规则设置",
"Kb7df6ac1": "拦截",
"K5c1722fe": "放行",
"K28435c5c": "路由详情",
"Ka9c08390": "只允许上传PNG、JPG或SVG格式的图片",
"K413b9869": "服务名称",
"K9919285b": "服务类型",
"Kcef64f4d": "默认 AI 供应商",
"Kcab588a9": "未配置任何 AI 模型供应商,",
"Kb9b56111": "立即配置",
"Kcf756b7a": "API 调用前缀",
"K13edc043": "作为服务内所有API的前缀,比如host/{service_name}/{api_path},一旦保存无法修改",
"Kf52a584d": "所属服务分类",
"K72b21be5": "设置服务展示在服务市场中的哪个分类下",
"Kdc840242": "图标",
"K427a5bd5": "仅支持 .png .jpg .jpeg .svg 格式的图片文件, 大于 1KB 的文件将被压缩",
"K44bc352d": "Logo",
"Kde6bae17": "删除服务",
"K885ea699": "删除操作不可恢复,请谨慎操作!",
"Kda8d5ea1": "上游",
"K12f58863": "服务提供了高性能 API 网关,并且可以无缝接入多种大型 AI 模型,并将这些 AI 能力打包成 API 进行调用,从而大幅简化了 AI 模型的使用门槛。同时,我们的平台提供了完善的 API 管理功能,支持 API 的创建、监控、访问控制等,保障开发者可以高效、安全地开发和管理 API 服务。",
"K2d6658ed": "添加服务",
"K7b8f623f": "输入名称、ID、所属团队、负责人查找服务",
"Kad98e030": "上游类型",
"Kdd9b5008": "后端默认使用的IP地址",
"Kc9acdb25": "负载均衡",
"K632dba5c": "转发 Host",
"Kc1f08a63": "重写域名",
"K628f6851": "超时时间",
"Kaff62621": "超时重试次数",
"Kf14d159b": "次",
"Kc41ca30e": "调用频率限制",
"K753e8aeb": "次/秒",
"K813e1c0a": "团队名称",
"K692f5aa6": "团队 ID",
"K5de0bc2": "团队 ID(team_id)可用于检索团队,一旦保存无法修改。",
"Ka63dd985": "团队负责人",
"Ka6bcd272": "负责人对团队内的团队、服务、成员有管理权限",
"Ka2012bdd": "删除团队",
"Kbde1f3d": "服务数据清除后,方可删除",
"K395acc14": "移除成员",
"Kec46a57f": "添加成员",
"K48724410": "输入姓名查找",
"Kb9052305": "搜索用户名、邮箱",
"K5ece3bac": "设置团队和成员,然后你可以在团队内创建服务和应用、订阅API,成员只能看到所属团队内的服务和应用。",
"K510cdd27": "添加团队",
"K9244ae14": "输入名称、ID、负责人查找团队",
"Kc7b24b4b": "配置团队",
"Kecb51e2c": "旧密码",
"K8266bcf2": "新密码",
"Ka9aef039": "确认密码",
"Kcf42dcda": "两次密码不一致",
"Kf876a42d": "修改密码",
"K8ed884f": "管理个人账号",
"K9be8e1d7": "API调用统计",
"K521ab28e": "选择服务",
"Kcc8265e1": "选择API",
"Kc380335f": "路径",
"K8aefc1e4": "请输入请求路径进行搜索",
"K50d471b2": "重置",
"Kee8ae330": "查询",
"Ka2c794a2": "导出",
"Kaf70c3b": "退出全屏",
"Kd22841a4": "(0)调用详情",
"K1512e983": "应用调用统计",
"Kb4d2007f": "请选择应用",
"K8c7f2d2e": "调用趋势",
"K657c3452": "(0)-(1)调用趋势",
"Kc04efb87": "调用量统计",
"Keb98266e": "加入总体数据对比",
"K18c2ed46": "(0)调用量",
"Kc3741830": "(0)调用成功率",
"Ka6aa5863": "请求总数",
"K9eaef42": "请求成功率",
"K7082a4af": "转发总数",
"K1ce386fb": "转发成功率",
"K87d6877e": "状态码4xx数",
"K4c8a54db": "状态码5xx数",
"Kd566283e": "调用总体趋势",
"K21ad4a6a": "(0)报文量",
"Kd23a0be6": "请求报文量",
"Kec3e8361": "响应报文量",
"Ke6250744": "状态码4XX数",
"K2d79d4e1": "状态码5XX数",
"Kcf6553c6": "服务调用统计",
"Kffcfe375": "请选择服务",
"Ka65f739c": "调用详情",
"K89b7ac79": "API 请求量 Top10",
"Kc0915603": "应用调用量 Top10",
"Kf90b54": "服务被调用量 Top10",
"Kfb26388": "暂无请求统计数据",
"Kc8cbd8f8": "请求统计",
"K8dece48": "暂无转发统计数据",
"K1ee32434": "转发统计",
"Kcd125e4d": "暂无调用量统计数据",
"Kaa114e8b": "暂无报文量统计数据",
"K3ad84406": "报文量统计",
"Kfa088d49": "集群配置并开启监控",
"K3da3b9a0": "监控功能用于辅助管理集群内信息,请配置集群、设置监控信息后查看当前集群监控情况;",
"Kaddacfb": "集群配置",
"K4ac33975": "配置集群地址,以确保监控系统能够正确识别和连接到集群",
"Ke5ed9810": "配置集群信息",
"K1a132228": "监控设置",
"K6af08c3c": "配置监控信息",
"K4a1a14": "监控总览",
"K69741ea7": "服务被调用统计",
"K9c8d9933": "API 调用统计",
"K145e4941": "亿",
"Ke6a935d": "万",
"Kd59290a2": "搜索分类或标签",
"K6b75bdbc": "暂无API数据",
"Kd8a7a689": "搜索或选择应用",
"K4b15d6f5": "申请理由",
"Kb7e869a4": "应用管理",
"Kb71b5a13": "鉴权类型",
"K4d1465ee": "Iss",
"K5dcd7ed8": "签名算法",
"K5b0eedd3": "Secret",
"K44f4ffe1": "RSA 公钥",
"Kc5ecd7d9": "用户名 JsonPath",
"K417d85cf": "校验字段",
"K3b82fe1d": "是否 Base64 加密",
"K49b5f4a3": "AK",
"K31418470": "SK",
"Kbfeb5297": "Apikey",
"K1a78e6f0": "过期时间",
"Ke64e43a": "隐藏鉴权信息",
"K5168eb63": "应用名称",
"K546e46f": "应用 ID",
"K95764d1d": "删除应用",
"K217cb125": "鉴权详情",
"K2bb63eca": "添加鉴权",
"Kd74d69b7": "编辑鉴权",
"K9cbe1e0": "修改",
"Kb6e9328f": "访问授权",
"Kd23d1716": "添加授权",
"K9dfa2c97": "永不过期",
"Kfa920c0": "到期时间",
"Kcce1af60": "订阅的服务",
"Kbeb4e991": "审批详情",
"K3118fdb0": "取消订阅",
"Ked811bb1": "请确认是否取消订阅?",
"K50c39a62": "取消订阅申请",
"K1856c229": "请确认是否取消订阅申请?",
"K66ea2f0": "搜索服务",
"Kfeb2559b": "审批中",
"K667bbbe7": "添加应用",
"Ka4b45550": "暂无服务描述",
"K3c7b175f": "订阅的服务数量:已通过 (0) 个,申请中 (1) 个",
"Kbe3e9335": "退出测试",
"K370a3eb2": "服务市场",
"Kf7ec36d": "服务详情",
"K58ca9485": "申请服务",
"K59cdbec3": "介绍",
"K4aa9ed2c": "申请",
"K6c060779": "服务信息",
"K8723422e": "接入应用",
"Kb97544cb": "供应方",
"Kb32f0afe": "分类",
"K81634069": "版本",
"Keefda53d": "更新时间",
"K96a2f1c8": "无标签",
"K72b0c0b3": "API 数量",
"K93d5a66e": "接入应用数量",
"K96059c69": "关联标签",
"K8b7c2592": "更新者",
"K32263abd": "添加 Open Api",
"K7829bb78": "配置 Open Api",
"Kcdf76005": "Open Api",
"Ke2601944": "调用服务",
"K8504bca8": "放大",
"K693c1b41": "缩小",
"K3d7465f7": "文件日志",
"Kc87167a0": "HTTP日志",
"K54630fe8": "Kafka日志",
"Kd5c3966e": "NSQ日志",
"K2e3de2c1": "Syslog日志",
"K48322168": "未分配",
"K98f247f9": "超级管理员",
"K9c8a571f": "团队管理员",
"K929b485b": "运维管理员",
"K82cc5ec2": "普通成员",
"Ke41d7451": "只读成员",
"Kf99e8b66": "服务管理员",
"Kda8db57a": "服务开发者",
"K216a1ac7": "应用开发者",
"K27924db": "应用管理员",
"K8dc5c723": "驱动名称",
"Kda249fe8": "请求失败数",
"Kcf2df651": "转发失败数",
"K7e6a859d": "作用范围",
"K3a008b34": "添加条目",
"Ke0599ef7": "添加地址",
"K48d3b5c4": "文件名称",
"Kafde0d2a": "存放目录",
"Kfb2926ac": "日志分割周期",
"Kd96c2c69": "单位:天",
"Kc2b776fa": "输出格式",
"K7b7cdac2": "格式化配置",
"K2f59807a": "服务器地址",
"Kb1cfa6e7": "Access日志",
"K540488a8": "NSQD地址列表",
"K8bc33a11": "鉴权Secret",
"K1cd3002f": "网络协议",
"Kdfaa32c8": "日志等级",
"Kc0408d9c": "单行",
"Ke3db239d": "小时",
"K3509a9f8": "天",
"Kb3960e83": "未发布",
"K8bd1e18": "待发布",
"K225a6c43": "单位:s,最小值:1",
"K6a7fa303": "无需审批",
"Kd196e8a4": "需要审批",
"K1b6777bb": "Basic URL",
"Kd55c6887": "申请审批",
"K300c89d4": "创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API"
}
@@ -359,7 +359,6 @@
"K91aa4801": "您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。",
"K480045ce": "Version (0)-(1)",
"Kadee8e49": "日志配置",
"K3453272": "APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。",
"K33c76dbc": "部门名称",
"K84829ca9": "父部门 ID",
"K4d7fc74b": "子部门名称",
@@ -623,5 +622,27 @@
"K71753476": "是否放行",
"K597435c5": "监控",
"Kde9d6e8e": "开启拦截后,网关会拦截该路径的请求",
"K4758140d": "路由列表"
"K4758140d": "路由列表",
"K12f58863": "服务提供了高性能 API 网关,并且可以无缝接入多种大型 AI 模型,并将这些 AI 能力打包成 API 进行调用,从而大幅简化了 AI 模型的使用门槛。同时,我们的平台提供了完善的 API 管理功能,支持 API 的创建、监控、访问控制等,保障开发者可以高效、安全地开发和管理 API 服务。",
"K8ab0fc95": "常规设置",
"K2724314b": "提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。",
"Kecbb0e45": "系统",
"Ka358e23d": "常规",
"K449058e9": "API 网关",
"K99935e6f": "AI 模型",
"K1deaa2dd": "用户",
"K4057391a": "集成",
"K408bfcf1": "进阶教程",
"K2cdbb773": "核心功能",
"K3378c50d": "账号与角色",
"Kda5bb930": "邀请你的团队成员加入 APIPark,共同管理和调用 API。",
"K62e89ee7": "权限管理",
"K3596804a": "如果需要调用某个服务的API,需要先订阅该服务,并且等待提供服务的团队审批后才可发起API请求。",
"K1c15bb2e": "提供服务的团队可以审批来自其他团队的订阅申请,审批通过后的应用才可发起 API 请求。",
"K3453272": "APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。",
"K1afaf20e": "了解 APIPark 如何更好地管理 API 和 AI",
"Kcef64f4d": "默认 AI 供应商",
"K13edc043": "作为服务内所有API的前缀,比如host/{service_name}/{api_path},一旦保存无法修改",
"Ka7bb958f": "AI",
"Kcb81cc64": "REST"
}
@@ -0,0 +1 @@
{}
+20
View File
@@ -0,0 +1,20 @@
export const extractIPFromURL = (url:string|string[]) =>{
if (Array.isArray(url)) {
url = url[0]; // 获取第一个 URL
}
if (typeof url !== 'string' || !url.includes("://")) {
console.warn("Invalid URL format");
return null;
}
const match = url.match(/https?:\/\/([\d.]+):\d+/);
return match ? match[1] : null;
}
export const isPrivateIP = (ip:string) =>{
if (typeof ip !== 'string') {
console.error("Invalid IP format");
return false;
}
const privateIpRegex = /^(10\.\d{1,3}\.\d{1,3}\.\d{1,3})$|^(172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3})$|^(192\.168\.\d{1,3}\.\d{1,3})$/;
return privateIpRegex.test(ip);
}
@@ -255,7 +255,7 @@ import { TopologyProjectItem, TopologyServiceItem } from "@core/pages/systemRun
// labelCfg: {
// style: {
// fill: '5B8FF9',
// opacity: 0 // 将透明度设置为0,隐藏提示信息,hover 才出现
// opacity: 0
// }
// }
// }
+13
View File
@@ -235,4 +235,17 @@ a{
background-color: transparent;
border:none;
}
}
.ant-menu .ant-menu-title-content{
display:unset !important;
}
.ai-setting-svg-container svg{
width: 100%;
height:100%;
display:block;
}
.ai-service-api-preview .swagger-ui h3.opblock-tag{
display: none;
}
+11 -1
View File
@@ -6,15 +6,25 @@ import {BreadcrumbProvider} from "@common/contexts/BreadcrumbContext.tsx";
import { StyleProvider } from '@ant-design/cssinjs';
import zhCN from 'antd/locale/zh_CN';
import enUS from 'antd/locale/en_US';
import zhTW from 'antd/locale/zh_TW';
import jaJP from 'antd/locale/ja_JP';
import useInitializeMonaco from "@common/hooks/useInitializeMonaco";
import { useEffect, useMemo, useState } from 'react';
import 'dayjs/locale/zh-cn';
import 'dayjs/locale/zh-tw';
import 'dayjs/locale/ja';
import dayjs from 'dayjs';
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
import { $t } from '@common/locales';
type Locale = ConfigProviderProps['locale'];
const languageMap: Record<string, Locale> = {
'zh-CN':zhCN,
'en-US':enUS,
'zh-TW':zhTW,
'ja-JP':jaJP
}
const antdComponentThemeToken = {
@@ -147,7 +157,7 @@ function App() {
useEffect(() => {
dayjs.locale(state.language);
setLocal(state.language === 'cn' ? zhCN : enUS);
setLocal(languageMap[state.language]);
},[state.language])
const validateMessages = useMemo(()=>({
@@ -109,8 +109,8 @@ const PUBLIC_ROUTES:RouteConfig[] = [
},
{
path:'service',
component:<SystemOutlet />,
key: uuidv4(),
component:<SystemOutlet />,
provider: SystemProvider,
children:[
{
@@ -143,6 +143,18 @@ const PUBLIC_ROUTES:RouteConfig[] = [
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideApiDocument.tsx')),
},
{
path:'route/create',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterCreate')),
},
{
path:'route/:routeId',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterCreate')),
},
{
path:'route',
key: uuidv4(),
@@ -216,121 +228,93 @@ const PUBLIC_ROUTES:RouteConfig[] = [
]
},
]
}
]
}
]
},
{
path:'aiservice',
component:<AiServiceOutlet />,
key: uuidv4(),
provider: AiServiceProvider,
children:[
{
path:'',
key:uuidv4(),
component:<Navigate to="list" />
},
{
path:'list',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceList.tsx')),
},
{
path:'list/:teamId',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceList.tsx')),
},
{
path:':teamId',
component:<Outlet/>,
key: uuidv4(),
children:[
},
{
path:'inside/:serviceId',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsidePage.tsx')),
children:[
{
path:'api',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideApiDocument')),
},
{
path:'route/create',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterCreate')),
},
{
path:'route/:routeId',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterCreate')),
},
{
path:'route',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterList')),
},
{
path:'document',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsideDocument.tsx')),
},
{
path:'subscriber',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsideSubscriber.tsx')),
children:[
]
},
{
path:'approval',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/approval/AiServiceInsideApproval')),
children:[
{
path:'',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/approval/AiServiceInsideApprovalList')),
},
{
path:'*',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/approval/AiServiceInsideApprovalList')),
}
]
},
{
path:'publish',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/publish/AiServiceInsidePublish')),
children:[
{
path:'',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/publish/AiServiceInsidePublishList')),
},
{
path:'*',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/publish/AiServiceInsidePublishList')),
}
]
},
{
path:'setting',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceConfig.tsx')),
children:[
]
},
]
}
path:'aiInside/:serviceId',
component:<AiServiceOutlet />,
provider: AiServiceProvider,
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsidePage.tsx')),
children:[
{
path:'api',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideApiDocument')),
},
{
path:'route/create',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterCreate')),
},
{
path:'route/:routeId',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterCreate')),
},
{
path:'route',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterList')),
},
{
path:'document',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsideDocument.tsx')),
},
{
path:'subscriber',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsideSubscriber.tsx')),
children:[
]
},
{
path:'approval',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/approval/AiServiceInsideApproval')),
children:[
{
path:'',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/approval/AiServiceInsideApprovalList')),
},
{
path:'*',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/approval/AiServiceInsideApprovalList')),
}
]
},
{
path:'publish',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/publish/AiServiceInsidePublish')),
children:[
{
path:'',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/publish/AiServiceInsidePublishList')),
},
{
path:'*',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/publish/AiServiceInsidePublishList')),
}
]
},
{
path:'setting',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemConfig.tsx')),
children:[
]
},
]
}
]
}
]
@@ -377,8 +361,8 @@ const PUBLIC_ROUTES:RouteConfig[] = [
}]
},
{
path:'servicecategories',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/serviceCategory/ServiceCategory.tsx')),
path:'commonsetting',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/common/CommonPage.tsx')),
key:uuidv4(),
},
{
@@ -1,61 +1,11 @@
import { AiServiceTableListItem, AiServiceRouterTableListItem, VariableItems } from "./type";
import { AiServiceRouterTableListItem, VariableItems } from "./type";
import { TabsProps } from "antd";
import { frontendTimeSorter } from "@common/utils/dataTransfer";
import { COLUMNS_TITLE, PLACEHOLDER } from "@common/const/const";
import { PageProColumns } from "@common/components/aoplatform/PageList";
export const AI_SERVICE_TABLE_COLUMNS: PageProColumns<AiServiceTableListItem>[] = [
{
title:('服务名称'),
dataIndex: 'name',
ellipsis:true,
width:160,
fixed:'left',
sorter: (a,b)=> {
return a.name.localeCompare(b.name)
},
},
{
title:('服务 ID'),
dataIndex: 'id',
width: 140,
ellipsis:true,
},
{
title:('AI 模型供应商'),
dataIndex: ['provider','name'],
ellipsis:true,
},
{
title:('所属团队'),
dataIndex: ['team','name'],
ellipsis:true,
// filters: true,
// onFilter: true,
// filterSearch: true,
},
{
title:('API 数量'),
dataIndex: 'apiNum',
ellipsis:true,
sorter: (a,b)=> {
return a.apiNum - b.apiNum
},
},
{
title: ('描述'),
dataIndex: 'description',
ellipsis:true,
},
{
title:('创建时间'),
dataIndex: 'createTime',
width:182,
ellipsis:true,
sorter: (a,b)=>frontendTimeSorter(a,b,'createTime')
}
];
export const AI_SERVICE_ROUTER_TABLE_COLUMNS: PageProColumns<AiServiceRouterTableListItem>[] = [
{
@@ -72,7 +22,7 @@ export const AI_SERVICE_ROUTER_TABLE_COLUMNS: PageProColumns<AiServiceRouterTabl
title:('模型'),
dataIndex: ['model','logo'],
ellipsis:true,
render: (_: React.ReactNode, entity: AiServiceRouterTableListItem) =><div className="flex items-center gap-[2px]" > <div className="flex items-center" dangerouslySetInnerHTML={{ __html: entity.model.logo }}></div><span>{entity.model.id}</span></div>
render: (_: React.ReactNode, entity: AiServiceRouterTableListItem) =><div className="flex items-center gap-[2px] " ><span>{entity.model.id}</span></div>
},
{
title:('描述'),
@@ -118,14 +68,21 @@ export const AI_SERVICE_VARIABLES_TABLE_COLUMNS: PageProColumns<VariableItems &
}
],
},
ellipsis:true
ellipsis:true,
fieldProps:{
allowClear:false
}
},
{
title:('描述'),
dataIndex: 'description',
key:'description',
formItemProps: {
className:'p-0 bg-transparent border-none'}
className:'p-0 bg-transparent border-none'
},
fieldProps:{
allowClear:false
}
},
{
title:('必填'),
@@ -3,17 +3,7 @@ import { FormInstance, UploadFile } from "antd";
import { EntityItem } from "@common/const/type";
import { SubscribeEnum, SubscribeFromEnum } from "./const";
export type AiServiceTableListItem = {
id:string;
name: string;
team: EntityItem;
apiNum: number;
description:string;
createTime:string;
updateTime:string;
canDelete:boolean;
provider:EntityItem
}
export type AiServiceConfigFieldType = {
name?: string;
@@ -28,6 +18,7 @@ export type AiServiceConfigFieldType = {
master?:string;
serviceType?:'public'|'inner';
catalogue?:string | string[];
approvalType?:string;
};
export type AiServiceSubServiceTableListItem = {
@@ -99,13 +99,16 @@ export const SYSTEM_TABLE_COLUMNS: PageProColumns<SystemTableListItem>[] = [
width: 140,
ellipsis:true,
},
{
title:('类型'),
dataIndex: 'service_kind',
width: 140,
ellipsis:true,
},
{
title:('所属团队'),
dataIndex: ['team','name'],
ellipsis:true,
// filters: true,
// onFilter: true,
// filterSearch: true,
},
{
title:('API 数量'),
@@ -349,6 +352,14 @@ export const SERVICE_VISUALIZATION_OPTIONS = [
{label:('内部服务:可通过网关访问,但不展示在服务广场'),value:'inner'},
{label:('公开服务:可通过网关访问,展示在服务广场,可被其他应用订阅'),value:'public'}];
export const SERVICE_APPROVAL_OPTIONS = [
{label:('无需审批:允许任何应用调用该服务'),value:'auto'},
{label:('人工审批:仅允许通过人工审批的应用调用该服务'),value:'manual'}];
export const SERVICE_KIND_OPTIONS = [
{label:('REST'),value:'rest'},
{label:('AI'),value:'ai'}];
export const SYSTEM_UPSTREAM_GLOBAL_CONFIG_TABLE_COLUMNS: PageProColumns<GlobalNodeItem & {_id:string}>[] = [
@@ -1,9 +1,7 @@
import { FormInstance, UploadFile } from "antd";
import { HeaderParamsType, BodyParamsType, QueryParamsType, RestParamsType, ResultListType, ApiBodyType } from "@common/const/api-detail";
import { EntityItem, MatchItem } from "@common/const/type";
import { SubscribeEnum, SubscribeFromEnum } from "./const";
import { HTTPMethod, Protocol } from "@common/components/postcat/api/RequestMethod";
export type SystemTableListItem = {
id:string;
@@ -13,6 +11,7 @@ export type SystemTableListItem = {
serviceNum: number,
description:string;
master:EntityItem;
service_kind:'ai'|'rest',
createTime:string;
};
@@ -27,7 +26,9 @@ export type SystemConfigFieldType = {
team?:string;
master?:string;
serviceType?:'public'|'inner';
serviceKind:'ai'|'rest';
catalogue?:string | string[];
approvalType?:string;
};
export type SystemSubServiceTableListItem = {
@@ -94,6 +95,7 @@ export type SystemApiProxyFieldType = {
protocols: string[];
id:string;
description?:string;
disable:boolean;
path:string;
methods:string[];
match:MatchItem[]
@@ -291,8 +293,6 @@ export type SystemInsideApiDocumentProps = {
export type SystemInsideApiProxyProps = {
className?:string
service:string
teamId:string
initProxyValue?:SystemApiProxyType
value?:SystemApiProxyType
type:'add'|'edit'
@@ -60,8 +60,8 @@ const AiServiceInsidePage:FC = ()=> {
const SYSTEM_PAGE_MENU_ITEMS = useMemo(()=>[
getItem($t('服务'), 'assets', null,
[
getItem(<Link to="./api">{$t('API')}</Link>, 'api',undefined,undefined,undefined,'team.service.api_doc.view'),
getItem(<Link to="./route">{$t('路由')}</Link>, 'route',undefined,undefined,undefined,'team.service.router.view'),
getItem(<Link to="./route">{$t('API 路由')}</Link>, 'route',undefined,undefined,undefined,'team.service.router.view'),
getItem(<Link to="./api">{$t('API 文档')}</Link>, 'api',undefined,undefined,undefined,'team.service.api_doc.view'),
getItem(<Link to="./document">{$t('使用说明')}</Link>, 'document',undefined,undefined,undefined,''),
getItem(<Link to="./publish">{$t('发布')}</Link>, 'publish',undefined,undefined,undefined,'team.service.release.view'),
],
@@ -95,7 +95,7 @@ const AiServiceInsidePage:FC = ()=> {
}
const filteredMenu = filterMenu(SYSTEM_PAGE_MENU_ITEMS as MenuItemGroupType<MenuItemType>[])
setActiveMenu((pre)=>{
return pre ?? 'api'
return pre ?? 'route'
})
return filteredMenu || []
},[accessData,accessInit, SYSTEM_PAGE_MENU_ITEMS])
@@ -111,7 +111,7 @@ const AiServiceInsidePage:FC = ()=> {
}else if(serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]){
setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1])
}else{
setActiveMenu('api')
setActiveMenu('route')
}
}, [currentUrl]);
@@ -123,7 +123,7 @@ const AiServiceInsidePage:FC = ()=> {
useEffect(()=>{
if( activeMenu && serviceId === currentUrl.split('/')[currentUrl.split('/').length - 1]){
navigateTo(`/aiservice/${teamId}/inside/${serviceId}/${activeMenu}`)
navigateTo(`/service/${teamId}/aiInside/${serviceId}/${activeMenu}`)
}
},[activeMenu])
@@ -137,7 +137,7 @@ const AiServiceInsidePage:FC = ()=> {
tagList={[{label:
<Paragraph className="mb-0" copyable={serviceId ? { text: serviceId } : false}>{$t('服务 ID')}{serviceId || '-'}</Paragraph>
}]}
backUrl="/aiservice/list">
backUrl="/service/list">
<div className="flex flex-1 h-full">
<Menu
onClick={onMenuClick}
@@ -121,7 +121,7 @@ const AiServiceInsideSubscriber:FC = ()=>{
useEffect(() => {
setBreadcrumb([
{
title:<Link to={`/aiservice/list`}>{$t('服务')}</Link>
title:<Link to={`/service/list`}>{$t('服务')}</Link>
},
{
title:$t('订阅方管理')
@@ -245,7 +245,7 @@ export const AiServiceSubscriberConfig = forwardRef<AiServiceSubscriberConfigHan
labelAlign='left'
scrollToFirstError
form={form}
className="mx-auto "
className="mx-auto "
name="systemInsideSubscriber"
autoComplete="off"
>
@@ -36,7 +36,7 @@ const AiServiceInsideApiDocument = forwardRef<AiServiceInsideApiDocumentHandle,A
const ApiPreview = ({spec}:{spec?:string | object})=>{
return (
<div className="h-full overflow-hidden">
<div className="flex-1 overflow-auto pr-PAGE_INSIDE_X">
<div className="flex-1 h-full overflow-auto pr-PAGE_INSIDE_X">
<ApiDocument spec={spec}/>
</div>
</div>
@@ -46,7 +46,7 @@ const AiServiceInsideApiDocument = forwardRef<AiServiceInsideApiDocumentHandle,A
return (<>
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} wrapperClassName=' h-full overflow-hidden '>
<div className=" h-full">
<div className=" h-full ai-service-api-preview">
{ apiDetail ? <ApiPreview spec={apiDetail} />
: <Empty image={EmptySVG} >
</Empty>}
@@ -63,12 +63,12 @@ const AiServiceInsideRouterCreate = () => {
return variablesTableRef?.current?.validateFields().then(()=>{
return form.validateFields().then((formValue)=>{
const {name, path, description, variables, prompt, timeout, retry} = formValue
const body = {name, path: !routeId && prefixForce ? `${apiPrefix}/${path}`:path , description,timeout, retry,aiPrompt:{variables:variables, prompt:prompt},aiModel:{id:defaultLlm?.id, config:defaultLlm?.config}}
const body = {name, path: prefixForce ? `${apiPrefix}/${path}`:path , description,timeout, retry,aiPrompt:{variables:variables, prompt:prompt},aiModel:{id:defaultLlm?.id, provider:defaultLlm?.provider, config:defaultLlm?.config}}
return fetchData<BasicResponse<null>>('service/ai-router',{method: routeId ? 'PUT' : 'POST',eoBody:(body), eoParams: {service:serviceId,team:teamId, ...(routeId ? {router:routeId}: {})},eoTransformKeys:['aiPrompt','aiModel']}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
navigator(`/aiservice/${teamId}/inside/${serviceId}/route`)
navigator(`/service/${teamId}/aiInside/${serviceId}/route`)
return Promise.resolve(true)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
@@ -94,7 +94,8 @@ const AiServiceInsideRouterCreate = () => {
const {path, aiPrompt,aiModel} = data.api
form.setFieldsValue({...data.api,...aiPrompt, path:prefixForce && path?.startsWith(apiPrefix + '/')? path.slice((apiPrefix?.length || 0) + 1) : path })
setVariablesTable(aiPrompt.variables as VariableItems[])
setDefaultLlm(prev => ({...prev, id:aiModel?.id, config:aiModel.config}) as (AiProviderDefaultConfig & { config: string; }))
setDefaultLlm(prev => ({...prev, provider: aiModel?.provider, id:aiModel?.id, config:aiModel.config}) as (AiProviderDefaultConfig & { config: string; }))
getDefaultModelConfig(aiModel?.provider)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
@@ -102,15 +103,16 @@ const AiServiceInsideRouterCreate = () => {
.finally(()=>setLoading(false))
}
const getDefaultModelConfig = ()=>{
fetchData<BasicResponse<{llms:AiProviderLlmsItems[],provider:AiProviderDefaultConfig}>>('ai/provider/llms',{method:'GET',eoParams:{provider:aiServiceInfo?.provider?.id}, eoTransformKeys:['default_llm']}).then(response=>{
const getDefaultModelConfig = (provider?:string)=>{
fetchData<BasicResponse<{llms:AiProviderLlmsItems[],provider:AiProviderDefaultConfig}>>('ai/provider/llms',{method:'GET',eoParams:{provider:provider ?? aiServiceInfo?.provider?.id}, eoTransformKeys:['default_llm']}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setLlmList(data.llms)
setDefaultLlm(prev => {
const llmSetting = data.llms?.find((x:AiProviderLlmsItems)=>x.id ===( prev?.id ?? data.provider.defaultLlm))
return {...prev,
defaultLlm:data.provider.defaultLlm,
defaultLlm:data.provider.defaultLlm,
provider:data.provider.id,
name:data.provider.name,
config:llmSetting?.config || '',
...(llmSetting ?? {})
@@ -124,7 +126,7 @@ const AiServiceInsideRouterCreate = () => {
useEffect(()=>{
aiServiceInfo?.provider && getDefaultModelConfig()
!routeId && aiServiceInfo?.provider && getDefaultModelConfig()
},[
aiServiceInfo
])
@@ -175,7 +177,7 @@ const AiServiceInsideRouterCreate = () => {
const handlerSubmit:() => Promise<boolean>|undefined= ()=>{
return drawerAddFormRef.current?.save()?.then((res:{id:string, config:string})=>{
setDefaultLlm(prev => ({...prev, id:res.id, config:res.config, logo:llmList?.find((x:AiProviderLlmsItems)=>x.id === res.id)?.logo}) as (AiProviderDefaultConfig & { config: string; }))
setDefaultLlm(prev => ({...prev, provider:res.provider, id:res.id, config:res.config, logo:llmList?.find((x:AiProviderLlmsItems)=>x.id === res.id)?.logo}) as (AiProviderDefaultConfig & { config: string; }))
return true})
}
@@ -185,16 +187,16 @@ const AiServiceInsideRouterCreate = () => {
return (
<InsidePage pageTitle={ 'AI 路由设置'|| '-'}
<InsidePage pageTitle={ $t('AI 路由设置')|| '-'}
showBorder={false}
scrollPage={false}
className="overflow-y-auto"
backUrl={`/aiservice/${teamId}/inside/${serviceId}/route`}
backUrl={`/service/${teamId}/aiInside/${serviceId}/route`}
customBtn={
<div className="flex gap-btnbase items-center">
<div className="flex items-center gap-btnbase">
<Button icon={<Icon icon='ic:baseline-tune' height={18} width={18} />} iconPosition='end' onClick={()=>openDrawer('edit')}>
<div className="flex items-center gap-[4px]">
<span className="flex items-center " dangerouslySetInnerHTML={{__html: defaultLlm?.logo || ''}}></span>
<div className="flex items-center gap-[10px]">
<span className="flex items-center h-[24px] ai-setting-svg-container " dangerouslySetInnerHTML={{__html: defaultLlm?.logo || ''}}></span>
<span>{defaultLlm?.id || defaultLlm?.defaultLlm}</span>
{defaultLlm?.scopes?.map(x=><Tag >{x?.toLocaleUpperCase()}</Tag>)}
</div>
@@ -211,14 +213,14 @@ const AiServiceInsideRouterCreate = () => {
labelAlign='left'
scrollToFirstError
form={form}
className="mx-auto flex flex-col h-full"
className="flex flex-col h-full mx-auto"
name="AiServiceInsideRouterCreate"
onValuesChange={handleValuesChange}
onFinish={onFinish}
autoComplete="off"
>
<div className="">
<Row className="flex items-center gap-btnbase w-full justify-between">
<Row className="flex items-center justify-between w-full gap-btnbase">
<Form.Item<AiServiceRouterField>
className="flex-1"
label={$t("路由名称")}
@@ -247,7 +249,7 @@ const AiServiceInsideRouterCreate = () => {
</Form.Item>
<Form.Item<AiServiceRouterField>
label={<div className="w-full flex justify-between items-center"><span>{$t("变量")}</span><a className="flex items-center gap-[4px]" onClick={addVariable}><Icon icon="ic:baseline-add" width={16} height={16} />New</a></div>}
label={<div className="flex items-center justify-between w-full"><span>{$t("变量")}</span><a className="flex items-center gap-[4px]" onClick={addVariable}><Icon icon="ic:baseline-add" width={16} height={16} />New</a></div>}
name="variables"
className="[&>.ant-row>.ant-col>label]:w-full"
>
@@ -264,7 +266,7 @@ const AiServiceInsideRouterCreate = () => {
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t('输入这个接口的描述')}/>
</Form.Item>
<Row className="flex items-center gap-btnbase w-full justify-between">
<Row className="flex items-center justify-between w-full gap-btnbase">
<Form.Item<AiServiceRouterField>
className="flex-1"
label={$t("请求超时时间")}
@@ -105,7 +105,7 @@ const AiServiceInsideRouterList:FC = ()=>{
fixed:'right',
valueType: 'option',
render: (_: React.ReactNode, entity: AiServiceRouterTableListItem) => [
<TableBtnWithPermission access="team.service.router.edit" key="edit" btnType="edit" onClick={()=>{navigator(`/aiservice/${teamId}/inside/${serviceId}/route/${entity.id}`)}} btnTitle="编辑"/>,
<TableBtnWithPermission access="team.service.router.edit" key="edit" btnType="edit" onClick={()=>{navigator(`/service/${teamId}/aiInside/${serviceId}/route/${entity.id}`)}} btnTitle="编辑"/>,
<Divider type="vertical" className="mx-0" key="div3"/>,
<TableBtnWithPermission access="team.service.router.delete" key="delete" btnType="delete" onClick={()=>{openModal('delete',entity)}} btnTitle="删除"/>,
],
@@ -130,7 +130,7 @@ const AiServiceInsideRouterList:FC = ()=>{
useEffect(() => {
setBreadcrumb([
{
title:<Link to={`/aiservice/list`}>{$t('服务')}</Link>
title:<Link to={`/service/list`}>{$t('服务')}</Link>
},
{
title:$t('路由')
@@ -164,7 +164,7 @@ const AiServiceInsideRouterList:FC = ()=>{
dataSource={tableListDataSource}
addNewBtnTitle={$t('添加路由')}
searchPlaceholder={$t('输入 URL 查找路由')}
onAddNewBtnClick={()=>{navigator(`/aiservice/${teamId}/inside/${serviceId}/route/create`)}}
onAddNewBtnClick={()=>{navigator(`/service/${teamId}/aiInside/${serviceId}/route/create`)}}
addNewBtnAccess="team.service.router.add"
tableClickAccess="team.service.router.view"
manualReloadTable={manualReloadTable}
@@ -172,7 +172,7 @@ const AiServiceInsideRouterList:FC = ()=>{
onChange={() => {
setTableHttpReload(false)
}}
onRowClick={(row:AiServiceRouterTableListItem)=>navigator(`/aiservice/${teamId}/inside/${serviceId}/route/${row.id}`)}
onRowClick={(row:AiServiceRouterTableListItem)=>navigator(`/service/${teamId}/aiInside/${serviceId}/route/${row.id}`)}
tableClass="mr-PAGE_INSIDE_X "
/>
</>
@@ -1,9 +1,12 @@
import { Codebox } from "@common/components/postcat/api/Codebox"
import { PLACEHOLDER } from "@common/const/const"
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"
import { useFetch } from "@common/hooks/http"
import { $t } from "@common/locales"
import { AiProviderLlmsItems } from "@core/pages/aiSetting/AiSettingList"
import { Form, Select, Tag } from "antd"
import { forwardRef, useEffect, useImperativeHandle } from "react"
import { AiProviderDefaultConfig, AiProviderLlmsItems } from "@core/pages/aiSetting/AiSettingList"
import { SimpleAiProviderItem } from "@core/pages/system/SystemConfig"
import { Form, message, Select, Tag } from "antd"
import { DefaultOptionType } from "antd/es/select"
import { forwardRef, useEffect, useImperativeHandle, useState } from "react"
export type AiServiceRouterModelConfigHandle = {
save:()=>Promise<{id:string, config:string}>
@@ -21,17 +24,52 @@ type AiServiceRouterModelConfigField = {
const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle, AiServiceRouterModelConfigProps>((props, ref)=>{
const [form] = Form.useForm();
const {llmList,entity} = props
const {entity} = props
const [providerList, setProviderList]= useState<DefaultOptionType[]>([])
const [llmList, setLlmList]= useState<DefaultOptionType[]>([])
const {fetchData} = useFetch()
useImperativeHandle(ref, ()=>({
save:form.validateFields
})
)
useEffect(()=>{
getProviderList()
form.setFieldsValue(entity)
},[])
const getProviderList = ()=>{
setProviderList([])
fetchData<BasicResponse<{ providers: SimpleAiProviderItem[] }>>('simple/ai/providers',{method:'GET',eoTransformKeys:[]}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setProviderList(data.providers?.filter(x=>x.configured)?.map((x:SimpleAiProviderItem)=>{return {...x,
label: x.name, value:x.id
}}))
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
const getLlmList = (provider:string)=>{
fetchData<BasicResponse<{llms:AiProviderLlmsItems[],provider:AiProviderDefaultConfig}>>('ai/provider/llms',{method:'GET',eoParams:{provider}, eoTransformKeys:['default_llm']}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setLlmList(data.llms)
form.setFieldsValue({
id:data.provider.defaultLlm,
config:data.llms.find(x=>x.id===data.provider.defaultLlm)?.config})
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> console.error(errorInfo))
}
const handleChangeProvider = (provider:string)=>{
getLlmList(provider)
}
return (
<Form
layout='vertical'
@@ -42,7 +80,20 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
name="aiServiceInsideRouterModalConfig"
autoComplete="off"
>
<Form.Item<AiServiceRouterModelConfigField>
label={$t("模型供应商")}
name="provider"
rules={[{ required: true }]}
>
<Select className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.select)}
options={providerList}
onChange={(e)=>{
handleChangeProvider(e)
}}>
</Select>
</Form.Item>
<Form.Item<AiServiceRouterModelConfigField>
label={$t("模型")}
name="id"
@@ -52,11 +103,13 @@ const AiServiceRouterModelConfig = forwardRef<AiServiceRouterModelConfigHandle,
placeholder={$t(PLACEHOLDER.select)}
options={llmList?.map(x=>({
value:x.id,
label:<div className="flex items-center gap-[4px]">
<div className="flex items-center" dangerouslySetInnerHTML={{ __html: x.logo }}></div>
label:<div className="flex items-center gap-[10px]">
<span>{x.id}</span>
{x?.scopes?.map(s=><Tag >{s?.toLocaleUpperCase()}</Tag>)}
</div>}))}>
</div>}))}
onChange={(e)=>{
form.setFieldValue('config',llmList.find(x=>x.id===e)?.config)
}}>
</Select>
</Form.Item>
@@ -48,7 +48,7 @@ const AiServiceInsideApprovalList:FC = ()=>{
if(code === STATUS_CODE.SUCCESS){
const modalIns = modal.confirm({
title:type === 'approval' ? $t('审批') : $t('查看'),
content:<SubscribeApprovalModalContent ref={subscribeRef} data={{...data.approval} as SubscribeApprovalInfoType} type={type} serviceId={serviceId!} teamId={teamId!} inAiService/>,
content:<SubscribeApprovalModalContent ref={subscribeRef} data={{...data.approval} as SubscribeApprovalInfoType} type={type} serviceId={serviceId!} teamId={teamId!} inSystem/>,
onOk:()=>{
return subscribeRef.current?.save('pass').then((res)=>res === true && manualReloadTable())
},
@@ -143,7 +143,7 @@ const AiServiceInsideApprovalList:FC = ()=>{
useEffect(() => {
setBreadcrumb([
{
title:<Link to={`/aiservice/list`}>{$t('服务')}</Link>
title:<Link to={`/service/list`}>{$t('服务')}</Link>
},
{
title:$t('订阅审批')
@@ -28,7 +28,7 @@ const AiServiceInsidePublic:FC = ()=>{
useEffect(() => {
setBreadcrumb([
{
title:<Link to={`/aiservice/list`}>{$t('服务')}</Link>
title:<Link to={`/service/list`}>{$t('服务')}</Link>
},
{
title:$t('发布')
@@ -352,7 +352,7 @@ const AiServiceInsidePublicList:FC = ()=>{
useEffect(() => {
setBreadcrumb([
{
title:<Link to={`/aiservice/list`}>{$t('服务')}</Link>
title:<Link to={`/service/list`}>{$t('服务')}</Link>
},
{
title:$t('发布')
@@ -1,12 +1,11 @@
import { LoadingOutlined } from "@ant-design/icons";
import InsidePage from "@common/components/aoplatform/InsidePage";
import { BasicResponse, STATUS_CODE, RESPONSE_TIPS } from "@common/const/const";
import { EntityItem } from "@common/const/type";
import { useFetch } from "@common/hooks/http";
import { $t } from "@common/locales";
import { Icon } from "@iconify/react/dist/iconify.js";
import { App, Spin, Card, Tag, Select, Button, Empty } from "antd";
import { useEffect, useMemo, useRef, useState } from "react";
import { App, Spin, Card, Tag, Select, Button, Empty, Divider } from "antd";
import { memo, useEffect, useRef, useState } from "react";
import AiSettingModalContent, { AiSettingModalContentHandle } from "./AiSettingModal";
import WithPermission from "@common/components/aoplatform/WithPermission";
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
@@ -27,10 +26,12 @@ export type AiProviderLlmsItems = {
id:string
logo:string
scopes:('chat'|'completions')[]
config:string
}
export type AiProviderDefaultConfig = {
id:string
provider:string
name:string
logo:string
defaultLlm:string
@@ -49,11 +50,8 @@ const AiSettingList = ()=>{
const [aiSettingList, setAiSettingList] = useState<AiSettingListItem[]>([])
const [loading, setLoading] = useState<boolean>(false)
// const [updateLoading, setUpdateLoading] = useState<boolean>(false)
const [loadingDefaultModel, setLoadingDefaultModel] = useState<string>('')
const modalRef = useRef<AiSettingModalContentHandle>()
const {setAiConfigFlushed,accessData} = useGlobalContext()
const [llmMap, setLlmMap] = useState<Map<string, {loading:boolean, list:DefaultOptionType[]}>>(new Map<string, {loading:boolean, list:DefaultOptionType[]}>)
const [currentProvider, setCurrentProvider] = useState<string>()
const getAiSettingList = ()=>{
setLoading(true)
@@ -68,42 +66,6 @@ const AiSettingList = ()=>{
}).finally(()=>setLoading(false))
}
const getLlmList = (provider:AiSettingListItem)=>{
setLlmMap(prev=>{
const newMap = new Map(prev);
if(newMap.get(provider.id)){
newMap.get(provider.id)!.loading = true
}else{
newMap.set(provider.id, {loading:true,list:[]})
}
return newMap
})
fetchData<BasicResponse<{llms:AiProviderLlmsItems[]}>>(`ai/provider/llms`,{method:'GET',eoParams:{provider:provider.id}}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setLlmMap(prev=>{
const newMap = new Map(prev);
const llmDetail = newMap.get(provider.id)
llmDetail!.list = data.llms?.map((x:AiProviderLlmsItems)=>({
label:<div className="flex w-full items-center gap-[4px]">
<div className="flex items-center" dangerouslySetInnerHTML={{ __html: x.logo }} />
<span>{x.id}</span></div>,
value:x.id}))
return newMap
})
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).finally(()=>{
setLlmMap(prev=>{
const newMap = new Map(prev);
const llmDetail = newMap.get(provider.id)
llmDetail!.loading = false
return newMap
})
})
}
// 第一期暂时隐藏
// const updateModalList = ()=>{
@@ -158,33 +120,107 @@ const AiSettingList = ()=>{
})
}
const changeDefaultModel = (value: string, entity:AiSettingListItem) => {
setLoadingDefaultModel(entity.id)
return fetchData<BasicResponse<null>>(`ai/provider/default-llm`,{method:'PUT', eoBody:{llm:value}, eoParams:{provider:entity.id}}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
getAiSettingList()
message.success(msg || $t(RESPONSE_TIPS.success))
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).finally(()=>setLoadingDefaultModel(''))
};
const modelOptions = useMemo(()=>{
return currentProvider ? llmMap?.get(currentProvider)?.list : []
},[currentProvider,llmMap])
useEffect(() => {
getAiSettingList()
}, []);
const CardBox = memo(({provider}:{provider:AiSettingListItem})=>{
const [options, setOptions] = useState<DefaultOptionType[]>([])
const [loading, setLoading] = useState<boolean>(false)
const [defaultLlm, setDefaultLlm] = useState<string>(provider.defaultLlm)
const getLlmList = ()=>{
if(options.length > 0) return
setLoading(true)
fetchData<BasicResponse<{llms:AiProviderLlmsItems[]}>>(`ai/provider/llms`,{method:'GET',eoParams:{provider:provider.id}}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setOptions(data.llms?.map((x:AiProviderLlmsItems)=>({
label:<span className="w-full truncate">{x.id}</span>,
value:x.id})))
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).finally(()=>{
setLoading(false)
})
}
const changeDefaultModel = (value: string, entity:AiSettingListItem) => {
setLoading(true)
return fetchData<BasicResponse<null>>(`ai/provider/default-llm`,{method:'PUT', eoBody:{llm:value}, eoParams:{provider:entity.id}}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
setDefaultLlm(value)
message.success(msg || $t(RESPONSE_TIPS.success))
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).finally(()=>setLoading(false))
};
return (
<Card title={
<div className="flex items-center justify-between">
<div className="flex items-center h-[22px] ai-setting-svg-container" dangerouslySetInnerHTML={{ __html: provider.logo }} />
<Tag bordered={false} color={provider.configured ? 'green' : undefined} className="h-[22px] px-[4px] text-center">
{provider.configured ? $t('已配置') : $t('未配置')}
</Tag>
</div> }
className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible h-[156px] m-0 flex flex-col "
classNames={{header:'border-b-[0px] p-[20px] px-[24px]', body:"pt-0 flex-1"}}
>
<div className="flex flex-col justify-between h-full gap-btnbase ">
<div className="flex items-center w-full h-[32px] flex-1">{
provider.configured && <><label className="text-nowrap">{$t('默认')}</label>
<WithPermission access="system.devops.ai_provider.edit">
<Select
value={defaultLlm}
variant="borderless"
className="flex-1 overflow-hidden"
// style={{ width: '100%' }}
onChange={(value)=>changeDefaultModel(value, provider)}
options={options}
onFocus={()=> getLlmList()}
loading={loading }
/>
</WithPermission>
</>
}
</div>
<WithPermission access="system.devops.ai_provider.view">
<Button block icon={<Icon icon="ic:outline-settings" width={18} height={18}/>} onClick={()=>openModal(provider)} classNames={{icon:'h-[18px]'}}>{$t('设置')}</Button>
</WithPermission>
</div>
</Card>
)
});
const ModelCardArea = ({modelList,className}:{ modelList:AiSettingListItem[] ;className?:string})=>{
return ( <>
{ modelList.length > 0 ?
<div
className={className}
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
gap: '20px',
}}
>
{modelList.map((provider:AiSettingListItem)=><CardBox key={provider.id} provider={provider} />)}
</div> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>
}</>
)
}
return (<>
<InsidePage
className="pb-PAGE_INSIDE_B overflow-y-auto"
pageTitle={$t('AI 模型供应商')}
className="overflow-y-auto pb-PAGE_INSIDE_B"
pageTitle={$t('AI 模型管理')}
description={$t('配置好 AI 模型后,你可以使用对应的大模型来创建 AI 服务')}
showBorder={false}
scrollPage={false}
// customBtn={
@@ -202,55 +238,17 @@ const AiSettingList = ()=>{
// }
>
<Spin className="h-full" wrapperClassName="h-full pr-PAGE_INSIDE_X" indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading}>
{aiSettingList && aiSettingList.length > 0 ? <div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
gap: '20px',
}}
>
{aiSettingList.map((provider:AiSettingListItem)=>(
<Card title={
<div className="flex justify-between items-center">
<div className="flex items-center" dangerouslySetInnerHTML={{ __html: provider.logo }} />
<Tag bordered={false} color={provider.configured ? 'green' : undefined} className="h-[22px] px-[4px] text-center">
{provider.configured ? $t('已配置') : $t('未配置')}
</Tag>
</div> }
className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible h-[156px] m-0 flex flex-col "
classNames={{header:'border-b-[0px] p-[20px] px-[24px]', body:"pt-0 flex-1"}}
>
<div className="flex flex-col gap-btnbase h-full justify-between">
<div className="flex items-center w-full h-[32px]">
<label className="text-nowrap">{$t('默认')}</label>
<WithPermission access="system.devops.ai_provider.edit">
<Select
value={provider.defaultLlm}
variant="borderless"
style={{ width: '100%' }}
onChange={(value)=>changeDefaultModel(value, provider)}
labelRender={(props)=>{
return !props.label && !llmMap.get(provider.id)?.list?.length ?
<div className="flex items-center">
<div className="flex items-center" dangerouslySetInnerHTML={{__html:provider.defaultLlmLogo}}></div>
<span>{provider.defaultLlm}</span>
</div>: props.label }}
x options={modelOptions}
onFocus={()=>{if(!llmMap.get(provider.id)?.loading && !llmMap.get(provider.id)?.list?.length ){
getLlmList(provider)
setCurrentProvider(provider.id)
}}}
loading={llmMap.get(provider.id)?.loading || !!(loadingDefaultModel && loadingDefaultModel === provider.id )}
/>
</WithPermission>
</div>
<WithPermission access="system.devops.ai_provider.view">
<Button block icon={<Icon icon="ic:outline-settings" width={18} height={18}/>} onClick={()=>openModal(provider)} classNames={{icon:'h-[18px]'}}>{$t('设置')}</Button>
</WithPermission>
</div>
</Card>
))}
</div>:<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
{aiSettingList && aiSettingList.length > 0 ? <div>
<p className="text-[14px] text-[#666] mb-[4px] font-bold">{$t('已配置')}</p>
<ModelCardArea className="mb-[20px]" modelList={aiSettingList.filter((item)=>item.configured) || [] }/>
{
aiSettingList.filter((item)=>!item.configured).length > 0 && <>
<Divider style={{margin:'20px 0 !important;'}} />
<p className="text-[14px] text-[#666] mb-[4px] mt-[20px] font-bold">{$t('未配置')}</p>
<ModelCardArea modelList={aiSettingList.filter((item)=>!item.configured) || [] }/>
</>
}
</div>:<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
</Spin>
</InsidePage>
</>)
@@ -0,0 +1,90 @@
import WithPermission from "@common/components/aoplatform/WithPermission";
import { BasicResponse, STATUS_CODE, RESPONSE_TIPS, PLACEHOLDER } from "@common/const/const";
import { useFetch } from "@common/hooks/http";
import { $t } from "@common/locales";
import { App, Form, Input, Row, Button } from "antd";
import { useEffect } from "react";
type ApiRequestSettingFieldType = {
siteName:string
siteLogo:string
invokeAddress:string
platformName:string
}
export default function ApiRequestSetting(){
const { message } = App.useApp()
const [form] = Form.useForm();
const {fetchData} = useFetch()
const onFinish = () => {
form.validateFields().then((value)=>{
return fetchData<BasicResponse<null>>('system/general',{method:'POST',eoBody:(value),eoTransformKeys:['invokeAddress','siteName','siteLogo','platformName']}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
getSystemSetting()
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 getSystemSetting = ()=>{
fetchData<BasicResponse<{ general: ApiRequestSettingFieldType }>>('system/general',{method:'GET',eoTransformKeys:['site_name', 'site_logo','invoke_address','platform_name']}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
form.setFieldsValue(data.general)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
useEffect(() => {
getSystemSetting()
return (form.setFieldsValue({}))
}, []);
return (
<>
<WithPermission access='system.devops.system_setting.edit'>
<Form
layout='vertical'
labelAlign='left'
scrollToFirstError
form={form}
className={`mx-auto`}
name="teamConfig"
onFinish={onFinish}
autoComplete="off"
>
<Form.Item<ApiRequestSettingFieldType>
label={$t("API 调用地址")}
name="name"
rules={[{ required: true,whitespace:true }]}
extra={$t("API base URL 一般设置为API 网关的外部网络访问地址,或者是API网关绑定的域名。")}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
<Row className="mb-[10px]"
>
<WithPermission access='system.devops.system_setting.edit'>
<Button type="primary" htmlType="submit">
{$t('保存')}
</Button>
</WithPermission>
</Row>
</Form>
</WithPermission>
</>
)
}
@@ -0,0 +1,33 @@
import InsidePage from "@common/components/aoplatform/InsidePage";
import { $t } from "@common/locales";
import ServiceCategory from "./ServiceCategory";
import ApiRequestSetting from "./ApiRequestSetting";
import { Row, Col } from "antd";
export default function CommonPage(){
return (
<InsidePage
pageTitle={$t('常规设置')}
showBorder={false}
contentClassName="pr-PAGE_INSIDE_X"
scrollPage={false}
>
<Row className="mb-btnybase" >
<Col >
<span className="font-bold mr-[13px]">
{$t('API 请求设置')}
</span>
</Col>
</Row>
<ApiRequestSetting />
<Row className="mb-btnybase mt-[40px]">
<Col >
<span className="font-bold mr-[13px]">
{$t('服务分类')}
</span>
</Col>
</Row>
<ServiceCategory />
</InsidePage>
)
}
@@ -0,0 +1,265 @@
import TreeWithMore from "@common/components/aoplatform/TreeWithMore";
import WithPermission from "@common/components/aoplatform/WithPermission";
import { BasicResponse, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
import { PERMISSION_DEFINITION } from "@common/const/permissions";
import { useFetch } from "@common/hooks/http";
import { checkAccess } from "@common/utils/permission";
import { CategorizesType, ServiceHubCategoryConfigHandle } from "@market/const/serviceHub/type";
import { App, Button, Spin, Tree, TreeDataNode, TreeProps } from "antd";
import { DataNode } from "antd/es/tree";
import { Key, useEffect, useMemo, useRef, useState } from "react";
import { ServiceHubCategoryConfig } from "./ServiceHubCategoryConfig";
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext";
import { LoadingOutlined } from "@ant-design/icons";
import { cloneDeep } from "lodash-es";
import { Icon } from "@iconify/react/dist/iconify.js";
import { EntityItem } from "@common/const/type";
import { $t } from "@common/locales";
export default function ServiceCategory(){
const [gData, setGData] = useState<CategorizesType[]>([]);
const [cateData, setCateData] = useState<CategorizesType[]>([]);
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
const {message,modal} = App.useApp()
const {fetchData} = useFetch()
const addRef = useRef<ServiceHubCategoryConfigHandle>(null)
const addChildRef = useRef<ServiceHubCategoryConfigHandle>(null)
const renameRef = useRef<ServiceHubCategoryConfigHandle>(null)
const {accessData} = useGlobalContext()
const { setBreadcrumb } = useBreadcrumb()
const [loading, setLoading] = useState<boolean>(false)
const onDrop: TreeProps['onDrop'] = (info) => {
const dropKey = info.node.key;
const dragKey = info.dragNode.key;
const dropPos = info.node.pos.split('-');
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); // the drop position relative to the drop node, inside 0, top -1, bottom 1
const loop = (
data: TreeDataNode[],
key: React.Key,
callback: (node: TreeDataNode, i: number, data: TreeDataNode[]) => void,
) => {
for (let i = 0; i < data.length; i++) {
if (data[i].id === key) {
return callback(data[i], i, data);
}
if (data[i].children) {
loop(data[i].children!, key, callback);
}
}
};
const data = cloneDeep(gData);
// Find dragObject
let dragObj: TreeDataNode;
loop(data, dragKey, (item, index, arr) => {
arr.splice(index, 1);
dragObj = item;
});
if (!info.dropToGap) {
// Drop on the content
loop(data, dropKey, (item) => {
item.children = item.children || [];
// where to insert. New item was inserted to the start of the array in this example, but can be anywhere
item.children.unshift(dragObj);
});
} else {
let ar: TreeDataNode[] = [];
let i: number;
loop(data, dropKey, (_item, index, arr) => {
ar = arr;
i = index;
});
if (dropPosition === -1) {
// Drop on the top of the drop node
ar.splice(i!, 0, dragObj!);
} else {
// Drop on the bottom of the drop node
ar.splice(i! + 1, 0, dragObj!);
}
}
setGData(data);
sortCategories(data)
};
const dropdownMenu = (entity:CategorizesType) => [
{
key: 'addChildCate',
label: (
<WithPermission access="system.api_market.service_classification.add"><Button className="border-none p-0 flex items-center bg-transparent " onClick={()=>openModal('addChildCate',entity)}>
{$t('添加子分类')}
</Button></WithPermission>
),
},
{
key: 'renameCate',
label: (
<WithPermission access="system.api_market.service_classification.edit"><Button className=" border-none p-0 flex items-center bg-transparent " onClick={()=>openModal('renameCate',entity)}>
{$t('修改分类名称')}
</Button></WithPermission>
),
},
{
key: 'delete',
label: (
<WithPermission access="system.api_market.service_classification.delete"><Button className=" border-none p-0 flex items-center bg-transparent " onClick={()=>openModal('delete',entity)}>
{$t('删除')}
</Button></WithPermission>
),
},
];
const treeData = useMemo(() => {
setExpandedKeys([])
const loop = (data: CategorizesType[]): DataNode[] =>
data?.map((item) => {
if (item.children) {
setExpandedKeys(prev=>[...prev,item.id])
return {
title: <TreeWithMore
stopClick={false}
dropdownMenu={dropdownMenu(item as CategorizesType)}>{item.name}</TreeWithMore> ,
key: item.id, children: loop(item.children)
};
}
return {
title: <TreeWithMore
stopClick={false}
dropdownMenu={dropdownMenu(item as CategorizesType)}>{item.name}</TreeWithMore>,
key: item.id,
};
});
return loop(gData ?? [])
}, [gData]);
const isActionAllowed = (type:'addCate'|'addChildCate'|'renameCate'|'delete') => {
const actionToPermissionMap = {
'addCate': 'add',
'addChildCate': 'add',
'renameCate': 'edit',
'delete': 'delete'
};
const action = actionToPermissionMap[type];
const permission :keyof typeof PERMISSION_DEFINITION[0]= `system.api_market.service_classification.${action}`;
return !checkAccess(permission, accessData);
};
const openModal = (type:'addCate'|'addChildCate'|'renameCate'|'delete',entity?:CategorizesType)=>{
let title:string = ''
let content:string|React.ReactNode = ''
switch (type){
case 'addCate':
title=$t('添加分类')
content=<ServiceHubCategoryConfig WithPermission={WithPermission} ref={addRef} type={type} />
break;
case 'addChildCate':
title=$t('添加子分类')
content=<ServiceHubCategoryConfig WithPermission={WithPermission} ref={addChildRef} type={type} entity={entity} />
break;
case 'renameCate':
title=$t('重命名分类')
content=<ServiceHubCategoryConfig WithPermission={WithPermission} ref={renameRef} type={type} entity={entity} />
break;
case 'delete':
title=$t('删除')
content=$t(DELETE_TIPS.default)
break;
}
modal.confirm({
title,
content,
onOk:()=>{
switch (type){
case 'addCate':
return addRef.current?.save().then((res)=>{if(res === true) getCategoryList()})
case 'addChildCate':
return addChildRef.current?.save().then((res)=>{if(res === true) getCategoryList()})
case 'renameCate':
return renameRef.current?.save().then((res)=>{if(res === true) getCategoryList()})
case 'delete':
return deleteCate(entity!).then((res)=>{if(res === true) getCategoryList()})
}
},
width:600,
okText:$t('确认'),
okButtonProps:{
disabled : isActionAllowed(type)
},
cancelText:$t('取消'),
closable:true,
icon:<></>,
})
}
const deleteCate = (entity:CategorizesType)=>{
return new Promise((resolve, reject)=>{
fetchData<BasicResponse<null>>('catalogue',{method:'DELETE',eoParams:{catalogue:entity.id},}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> reject(errorInfo))
})
}
const sortCategories = (newData:CategorizesType[])=>{
setLoading(true)
fetchData<BasicResponse<null>>('catalogue/sort',{method:'PUT',eoBody:newData}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
getCategoryList()
}else{
setGData(cateData)
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).catch(()=>{setGData(cateData)}).finally(()=>{setLoading(false)})
}
const getCategoryList = ()=>{
setLoading(true)
fetchData<BasicResponse<{ catalogues:CategorizesType[],tags:EntityItem[]}>>('catalogues',{method:'GET'}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setGData(data.catalogues)
setCateData(data.catalogues)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).finally(()=>{setLoading(false)})
}
useEffect(()=>{
getCategoryList()
},[])
return (
<div className="border border-solid border-BORDER p-[20px] rounded-[10px] ">
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className=''>
<Tree
showIcon
draggable
blockNode
expandedKeys={expandedKeys}
onExpand={(expandedKeys:Key[])=>{setExpandedKeys(expandedKeys as string[])}}
onDrop={onDrop}
treeData={treeData}
/>
<WithPermission access="system.api_market.service_classification.add">
<Button type="link" className="mt-[12px] pl-[0px]" onClick={()=>openModal('addCate')}><Icon icon="ic:baseline-add" width="18" height="18" className='mr-[2px]'/>{$t('添加分类')}</Button>
</WithPermission>
</Spin>
</div>
)
}
@@ -0,0 +1,122 @@
import {App, Form, Input} from "antd";
import {forwardRef, useEffect, useImperativeHandle} from "react";
import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx";
import {useFetch} from "@common/hooks/http.ts";
import { ServiceHubCategoryConfigHandle, ServiceHubCategoryConfigFieldType, ServiceHubCategoryConfigProps } from "@market/const/serviceHub/type.ts"
import WithPermission from "@common/components/aoplatform/WithPermission";
import { $t } from "@common/locales";
export const ServiceHubCategoryConfig = forwardRef<ServiceHubCategoryConfigHandle,ServiceHubCategoryConfigProps>((props,ref)=>{
const { message } = App.useApp()
const [form] = Form.useForm();
const {type,entity} = props
const {fetchData} = useFetch()
const save:()=>Promise<boolean | string> = ()=>{
const url:string = 'catalogue'
let method:string
switch (type){
case 'addCate':
case 'addChildCate':
method = 'POST'
break;
case 'renameCate':
method = 'PUT'
break
}
return new Promise((resolve, reject)=>{
if(!url || !method){
reject($t(RESPONSE_TIPS.error))
return
}
form.validateFields().then((value)=>{
fetchData<BasicResponse<null>>(url,{method,eoBody:(value), eoParams:{ ...(type === 'renameCate' ? {catalogue:value.id} :undefined)}}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> reject(errorInfo))
}).catch((errorInfo)=> reject(errorInfo))
})
}
useImperativeHandle(ref, ()=>({
save
})
)
useEffect(() => {
switch(type){
case 'addCate':
form.setFieldsValue({})
break
case 'addChildCate':
form.setFieldsValue({parent:entity!.id})
break
case 'renameCate':
form.setFieldsValue(entity)
break
}
}, []);
return (
<WithPermission access={type === 'addCate'? 'system.api_market.service_classification.add': 'system.api_market.service_classification.edit'}>
<Form
layout='vertical'
scrollToFirstError
labelAlign='left'
form={form}
className="mx-auto "
name="serviceHubCategoryConfig"
autoComplete="off"
>
{type === 'renameCate' &&
<Form.Item<ServiceHubCategoryConfigFieldType>
label={$t("ID")}
name="id"
hidden
rules={[{ required: true,whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
}
{(type === 'addCate' || type === 'renameCate') &&
<Form.Item<ServiceHubCategoryConfigFieldType>
label={$t("分类名称")}
name="name"
rules={[{ required: true ,whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>}
{type === 'addChildCate' &&<>
<Form.Item<ServiceHubCategoryConfigFieldType>
label={$t("父分类 ID")}
name="parent"
hidden
rules={[{ required: true,whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
<Form.Item<ServiceHubCategoryConfigFieldType>
label={$t("子分类名称")}
name="name"
rules={[{ required: true ,whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
</>
}
</Form>
</WithPermission>
)
})
+166 -52
View File
@@ -8,15 +8,123 @@ import { useLocation, useNavigate } from "react-router-dom"
export default function Guide(){
const [showGuide, setShowGuide] = useState(localStorage.getItem('showGuide') !== 'false' )
const [showAdvancedGuide, setShowAdvancedGuide] = useState(localStorage.getItem('showAdvancedGuide') !== 'false' )
const [, forceUpdate] = useState<unknown>(null);
const {state} = useGlobalContext()
const location = useLocation()
const currentUrl = location.pathname
const navigator = useNavigate()
const guideSections = [
{
title: $t('快速接入 AI'),
items: [
{
title: $t("配置你的 AI 模型"),
description: $t('通过 APIPark 快速接入各种 AI 模型,使用统一的格式来调用API,并且可以随意切换模型。'),
link: 'https://docs.apipark.com/docs/quick/pre-work/team'
},
{
title: $t("创建 AI 服务和 API"),
description: $t('创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。'),
link: 'https://docs.apipark.com/docs/quick/provider/service'
},
{
title: $t("创建调用 Token"),
description: $t('为了安全地调用 API,你需要创建一个应用以及Token。'),
link: 'https://docs.apipark.com/docs/quick/suberscriber/application'
},
{
title: $t("调用"),
description: $t('现在你可以通过 Token 来调用这些 API。'),
link: 'https://docs.apipark.com/docs/quick/suberscriber/application'
}
]
},
{
title: $t('快速接入 REST API'),
items: [
{
title: $t("创建 REST 服务和 API"),
description: $t('创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。'),
link: 'https://docs.apipark.com/docs/tutorials/api-market/service'
},
{
title: $t("创建调用 Token"),
description: $t('为了安全地调用 API,你需要创建一个应用以及Token。'),
link: 'https://docs.apipark.com/docs/quick/suberscriber/subscribe'
},
{
title: $t("调用"),
description: $t('现在你可以通过 Token 来调用这些 API。'),
link: 'https://docs.apipark.com/docs/quick/provider/approve'
}
]
},
{
title: $t('仪表盘'),
items: [
{
title: $t("统计 API 调用情况"),
description: $t('仪表盘中提供了多种统计图表,帮助我们了解 API 的运行情况。'),
link: 'https://docs.apipark.com/docs/quick/pre-work/monitor'
}
]
}
];
const advanceGuideSections = [
{
title: $t('核心功能'),
items: [
{
title: $t("账号与角色"),
description: $t('邀请你的团队成员加入 APIPark,共同管理和调用 API。'),
link: 'https://docs.apipark.com/docs/quick/pre-work/team'
},
{
title: $t("团队"),
description: $t('团队中包含了人员、应用和服务,不同团队之间的应用和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。'),
link: 'https://docs.apipark.com/docs/quick/provider/service'
},
{
title: $t("服务"),
description: $t('服务内包含一组 API,并且可以发布到 API 市场被其他团队使用。'),
link: 'https://docs.apipark.com/docs/quick/suberscriber/application'
}
]
},
{
title: $t('权限管理'),
items: [
{
title: $t("订阅服务"),
description: $t('如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审批后才可发起 API 请求。'),
link: 'https://docs.apipark.com/docs/tutorials/api-market/service'
},
{
title: $t("审批订阅申请"),
description: $t('提供服务的团队可以审批来自其他团队的订阅申请,审批通过后的应用才可发起 API 请求。'),
link: 'https://docs.apipark.com/docs/quick/suberscriber/subscribe'
}
]
},
{
title: $t('集成'),
items: [
{
title: $t("日志"),
description: $t('APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。'),
link: 'https://docs.apipark.com/docs/quick/pre-work/monitor'
}
]
}
];
useEffect(()=>{
localStorage.setItem('showGuide', showGuide.toString())
},[showGuide])
useEffect(()=>{
localStorage.setItem('showAdvancedGuide', showAdvancedGuide.toString())
},[showAdvancedGuide])
useEffect(()=>{
if(currentUrl === '/guide'){
setTimeout(()=>{
@@ -40,7 +148,8 @@ export default function Guide(){
scrollPage={false}
contentClassName=" w-full pr-PAGE_INSIDE_X pb-PAGE_INSIDE_B"
>
{showGuide &&
<div className="flex flex-col gap-[15px]">
{showGuide &&
<Collapse
size="large"
expandIconPosition='end'
@@ -52,65 +161,70 @@ export default function Guide(){
<p className="text-[14px] mb-[10px] flex gap-[8px] items-center font-bold">
<span>🚀</span><span>{`${$t('快速入门')}`}</span> </p>
<p className="text-[12px]" >{$t("我们提供了一些任务来帮你快速了解 APIPark")}</p></div>,
children:<QuickGuideContent changeGuideShow={setShowGuide} /> }]}
children:<QuickGuideContent changeGuideShow={setShowGuide} guideSections={guideSections} /> }]}
/>}
{showAdvancedGuide &&
<Collapse
size="large"
expandIconPosition='end'
defaultActiveKey={['1']}
className="bg-[linear-gradient(153.41deg,rgba(244,245,255,1)_0.23%,rgba(255,255,255,1)_83.32%)] rounded-[10px] [&>.ant-collapse-item>.ant-collapse-content]:bg-transparent "
items={[{ key: '1',
label:
<div className="">
<p className="text-[14px] mb-[10px] flex gap-[8px] items-center font-bold">
<span>🏍</span><span>{`${$t('进阶教程')}`}</span> </p>
<p className="text-[12px]" >{$t("了解 APIPark 如何更好地管理 API 和 AI")}</p></div>,
children:<QuickGuideContent changeGuideShow={setShowAdvancedGuide} guideSections={advanceGuideSections} /> }]}
/>}
</div>
</InsidePage>)
}
const QuickGuideContent = ({changeGuideShow}:{changeGuideShow:Dispatch<SetStateAction<boolean>>})=>{
const QuickGuideContent = ({changeGuideShow,guideSections}:{changeGuideShow:Dispatch<SetStateAction<boolean>>,guideSections: {
title: string;
items: {
title: string;
description: string;
link: string;
}[];
}[]})=>{
return (<>
<div className="">
<p className="flex gap-[8px] items-center text-[14px] font-bold"><Icon icon="ic:baseline-info" width="18" height="18" className="text-theme "
/>{$t('')}</p>
<div className="ml-[9px] border-[0px] border-l-[1px] my-[10px] border-dashed border-BORDER">
<div className="grid gap-[20px] px-[20px] py-[10px] justify-start content-start" style={{
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 0fr))',
gridAutoRows: '1fr'
}}>
<Card title={$t("团队")} className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer w-[300px] transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.05]" classNames={{header:'border-b-[0px] p-[20px] pb-[10px] text-[14px] font-normal ', body:"p-[20px] pt-0 text-[12px] text-[#666]"}} onClick={()=>{window.open('https://docs.apipark.com/docs/quick/pre-work/team','_blank')}}>
<span className="">{$t('团队中包含了人员、应用和服务,不同团队之间的应用和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。')}</span>
</Card>
<Card title={$t("服务")} className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer w-[300px] transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.05]" classNames={{header:'border-b-[0px] p-[20px] pb-[10px] text-[14px] font-normal', body:"p-[20px] pt-0 text-[12px] text-[#666]"}} onClick={()=>{window.open('https://docs.apipark.com/docs/quick/provider/service','_blank')}}>
<span className="">{$t('服务内包含一组 API,并且可以发布到 API 市场被其他团队使用。')}</span>
</Card>
<Card title={$t("应用")} className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer w-[300px] transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.05]" classNames={{header:'border-b-[0px] p-[20px] pb-[10px] text-[14px] font-normal', body:"p-[20px] pt-0 text-[12px] text-[#666]"}} onClick={()=>{window.open('https://docs.apipark.com/docs/quick/suberscriber/application','_blank')}}>
<span className="">{$t('应用是申请服务和调用 API 的身份,可以在 API 市场申请调用服务,并且每个应用拥有独立的 API 访问鉴权。')}</span>
</Card>
</div>
</div>
<p className="flex gap-[8px] items-center text-[14px] font-bold"><Icon icon="ic:baseline-info" width="18" height="18" className="text-theme"/>{$t('API 市场')}</p>
<div className="ml-[9px] border-[0px] border-l-[1px] my-[10px] border-dashed border-BORDER">
<div className="grid gap-[20px] px-[20px] py-[10px] justify-start content-start" style={{
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 0fr))',
gridAutoRows: '1fr'
}}>
<Card title={$t("检索服务和 API")} className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer w-[300px] transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.05]" classNames={{header:'border-b-[0px] p-[20px] pb-[10px] text-[14px] font-normal', body:"p-[20px] pt-0 text-[12px] text-[#666]"}} onClick={()=>{window.open('https://docs.apipark.com/docs/tutorials/api-market/service','_blank')}}>
<span className="">{$t('你可以在 API 市场中查看所有公开的服务。')}</span>
</Card>
<Card title={$t("订阅服务")} className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer w-[300px] transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.05]" classNames={{header:'border-b-[0px] p-[20px] pb-[10px] text-[14px] font-normal', body:"p-[20px] pt-0 text-[12px] text-[#666]"}} onClick={()=>{window.open('https://docs.apipark.com/docs/quick/suberscriber/subscribe','_blank')}}>
<span className="">{$t('如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审批后才可发起 API 请求。')}</span>
</Card>
<Card title={$t("审批订阅申请")} className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer w-[300px] transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.05]" classNames={{header:'border-b-[0px] p-[20px] pb-[10px] text-[14px] font-normal', body:"p-[20px] pt-0 text-[12px] text-[#666]"}} onClick={()=>{window.open('https://docs.apipark.com/docs/quick/provider/approve','_blank')}}>
<span className="">{$t('提供服务的团队可以审批来自其他团队的订阅申请,审批通过后的应用才可发起 API请求。')}</span>
</Card>
</div>
</div>
<p className="flex gap-[8px] items-center text-[14px] font-bold"><Icon icon="ic:baseline-info" width="18" height="18" className="text-theme"/>{$t('仪表盘')}</p>
<div className="ml-[9px] border-[0px] border-l-[1px] my-[10px] border-dashed border-BORDER">
<div className="grid gap-[20px] px-[20px] py-[10px] justify-start content-start" style={{
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 0fr))',
gridAutoRows: '1fr'
}}>
<Card title={$t("运行视图")} className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer w-[300px] transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.05]" classNames={{header:'border-b-[0px] p-[20px] pb-[10px] text-[14px] font-normal', body:"p-[20px] pt-0 text-[12px] text-[#666]"}} onClick={()=>{window.open('https://docs.apipark.com/docs/quick/pre-work/monitor','_blank')}}>
<span className="">{$t('仪表盘中提供了多种统计图表,帮助我们了解 API 的运行情况。')}</span>
</Card>
</div>
</div>
{guideSections.map((section, index) => (
<div key={index}>
<p className="flex gap-[8px] items-center text-[14px] font-bold">
<Icon icon="ic:baseline-info" width="18" height="18" className="text-theme" />
{section.title}
</p>
<div className="ml-[9px] border-[0px] border-l-[1px] my-[10px] border-dashed border-BORDER">
<div className="grid gap-[20px] px-[20px] py-[10px] justify-start content-start" style={{
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 0fr))',
gridAutoRows: '1fr'
}}>
{section.items.map((item, itemIndex) => (
<Card
key={itemIndex}
title={item.title}
className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer w-[300px] transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.05]"
classNames={{ header: 'border-b-[0px] p-[20px] pb-[10px] text-[14px] font-normal', body: "p-[20px] pt-0 text-[12px] text-[#666]" }}
onClick={() => { window.open(item.link, '_blank') }}
>
<span>{item.description}</span>
</Card>
))}
</div>
</div>
</div>
))}
<p className="flex gap-[8px] items-center">
<Icon icon="ic:baseline-info" width="18" height="18" className="text-theme"/>
<div className="flex justify-between items-center w-full">
<div className="flex items-center w-full gap-4">
<Button type="link" icon={<Icon icon="ic:baseline-open-in-new" width="18" height="18" />} iconPosition="end" classNames={{icon:'h-[22px] flex items-center'}} href="https://docs.apipark.com" target="_blank" className="text-[14px] font-bold px-0">{$t('了解更多功能')}</Button>
<Button type="text" icon={<Icon icon="ic:baseline-visibility-off" width="18" height="18" />} onClick={()=>changeGuideShow((prev)=>!prev)} classNames={{icon:'h-[22px] flex items-center'}} className="text-[14px] font-bold">{$t('隐藏快速入门')}</Button>
<Button type="text" icon={<Icon icon="ic:baseline-visibility-off" width="18" height="18" />} onClick={()=>changeGuideShow((prev)=>!prev)} classNames={{icon:'h-[22px] flex items-center'}} className="text-[14px] font-bold">{$t('隐藏该教程')}</Button>
</div>
</p>
</div>
@@ -69,7 +69,7 @@ const LogSettings = ()=>{
<Skeleton className='m-btnbase w-calc-100vw-minus-padding-r' active loading={loading}>
<InsidePage
pageTitle={$t('日志配置')}
description={$t("APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。")}
description={'APIPark '+$t("提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。")}
>
<div className="flex h-full">
<Menu
@@ -41,12 +41,12 @@ export const MemberDropdownModal = forwardRef<MemberDropdownModalHandle,MemberDr
return
}
form.validateFields().then((value)=>{
fetchData<BasicResponse<null>>(url,
fetchData<BasicResponse<null>>(url,
{method,
...(type !== 'addDep' && type !== 'addMember' && {eoParams: {id:entity!.id}}),
eoBody:({
...value,
...(value?.departmentIds ?{ departmentIds:Array.isArray(value?.departmentIds)? value?.departmentIds : [value?.departmentIds]}:{}),
...(type !== 'addDep' && type !== 'addMember' && {eoParams: {id:entity!.id}})
}),eoTransformKeys:['departmentIds']}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
@@ -154,14 +154,14 @@ export const MemberDropdownModal = forwardRef<MemberDropdownModalHandle,MemberDr
name="name"
rules={[{required: true,whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
<Input className="w-INPUT_NORMAL" disabled={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" disabled={type ==='editMember'} placeholder={$t(PLACEHOLDER.input)}/>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
<Form.Item<MemberDropdownModalFieldType>
label={$t("部门")}
@@ -169,7 +169,6 @@ export const MemberDropdownModal = forwardRef<MemberDropdownModalHandle,MemberDr
>
<TreeSelect
className="w-INPUT_NORMAL"
disabled={type ==='editMember'}
fieldNames={{label:'name',value:'id',children:'children'}}
showSearch
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
@@ -139,7 +139,7 @@ const MemberList = ()=>{
fixed:'right',
valueType: 'option',
render: (_: React.ReactNode, entity: MemberTableListItem) => [
<TableBtnWithPermission access="system.organization.role.system.edit" key="edit" btnType="edit" onClick={()=>{openModal('editMember',entity)}} btnTitle="编辑"/>,
<TableBtnWithPermission access="system.organization.member.edit" key="edit" btnType="edit" onClick={()=>{openModal('editMember',entity)}} btnTitle="编辑"/>,
],
}
]
@@ -9,6 +9,7 @@ import { ClusterNodeModal } from "./PartitionInsideClusterNode.tsx";
import { LoadingOutlined } from "@ant-design/icons";
import InsidePage from "@common/components/aoplatform/InsidePage.tsx";
import { $t } from "@common/locales/index.ts";
import { extractIPFromURL, isPrivateIP} from '@common/utils/ip.ts'
const PartitionInsideCluster:FC = ()=> {
const {setBreadcrumb} = useBreadcrumb()
@@ -86,11 +87,24 @@ const PartitionInsideCluster:FC = ()=> {
)
}
const IpTypeTag = ({ip:url}:{ip:string}) =>{
const ip = extractIPFromURL(url);
const isPrivate :boolean= ip ? isPrivateIP(ip) : false
return (
<span className={`px-[4px] py-[2px] text-[#fff] text-[12px] leading-[16px] rounded m-0 ${isPrivate ? 'bg-[#87d068]' : 'bg-[#3d46f2]'}`}>
{isPrivate ? '私有网络' : '公共网络'}
</span>
)
}
export function ClusterConfigPreview (x:PartitionClusterNodeTableListItem){
return <div className="flex flex-col gap-[4px] ">
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('管理地址')}</Col><Col>{x.managerAddress.map(m=>(<p className="leading-[22px]">{m}</p>))}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('服务地址')}</Col><Col>{x.serviceAddress.map(m=>(<p className="leading-[22px]">{m}</p>))}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('同步地址')}</Col><Col><p className="leading-[22px]">{x.peerAddress}</p></Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('管理地址')}</Col><Col>{x.managerAddress.map(m=>(
<p className="leading-[22px] flex items-center gap-[5px]"><IpTypeTag ip={m} />{m}</p>))}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('服务地址')}</Col><Col>{x.serviceAddress.map(m=>(
<p className="leading-[22px] flex items-center gap-[5px]"><IpTypeTag ip={m} />{m}</p>))}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('同步地址')}</Col><Col>
<p className="leading-[22px] flex items-center gap-[5px]"><IpTypeTag ip={x.peerAddress} />{x.peerAddress}</p></Col></Row>
</div>}
export default PartitionInsideCluster
@@ -1,7 +1,7 @@
import {forwardRef, useEffect, useImperativeHandle, useMemo, useState} from "react";
import {App, Button, Form, Input, Radio, Row, Select, TreeSelect, Upload} from "antd";
import { Link, useNavigate, useParams} from "react-router-dom";
import { Link, useLocation, useNavigate, useParams} from "react-router-dom";
import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx";
import {BasicResponse, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx";
import {useFetch} from "@common/hooks/http.ts";
@@ -10,10 +10,10 @@ import { EntityItem, MemberItem, SimpleTeamItem} from "@common/const/type.ts";
import { v4 as uuidv4 } from 'uuid'
import { SystemConfigFieldType, SystemConfigHandle } from "../../const/system/type.ts";
import { validateUrlSlash } from "@common/utils/validate.ts";
import { compressImage, normFile } from "@common/utils/uploadPic.ts";
import { normFile } from "@common/utils/uploadPic.ts";
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx";
import { useSystemContext } from "../../contexts/SystemContext.tsx";
import { SERVICE_VISUALIZATION_OPTIONS } from "@core/const/system/const.tsx";
import { SERVICE_APPROVAL_OPTIONS, SERVICE_KIND_OPTIONS, SERVICE_VISUALIZATION_OPTIONS } from "@core/const/system/const.tsx";
import { RcFile, UploadChangeParam, UploadFile, UploadProps } from "antd/es/upload/interface";
import { LoadingOutlined } from "@ant-design/icons";
import { getImgBase64 } from "@common/utils/dataTransfer.ts";
@@ -22,8 +22,13 @@ import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
import { Icon } from "@iconify/react/dist/iconify.js";
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
import { $t } from "@common/locales/index.ts";
import { AiServiceConfigFieldType } from "@core/const/ai-service/type.ts";
const MAX_SIZE = 2 * 1024; // 1KB
export type SimpleAiProviderItem = EntityItem & {
configured:boolean
logo:string
}
const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
const { message,modal } = App.useApp()
@@ -35,17 +40,45 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
const navigate = useNavigate();
const {setBreadcrumb} = useBreadcrumb()
const { setSystemInfo} = useSystemContext()
const [showClassify, setShowClassify] = useState<boolean>()
const [showClassify, setShowClassify] = useState<boolean>(true)
const [showAI, setShowAI] = useState<boolean>(false)
const [imageBase64, setImageBase64] = useState<string | null>(null);
const [tagOptionList, setTagOptionList] = useState<DefaultOptionType[]>([])
const [serviceClassifyOptionList, setServiceClassifyOptionList] = useState<DefaultOptionType[]>()
const [uploadLoading, setUploadLoading] = useState<boolean>(false)
const {checkPermission,accessInit, getGlobalAccessData,state} = useGlobalContext()
const {checkPermission,accessInit, getGlobalAccessData,state, aiConfigFlushed, setAiConfigFlushed} = useGlobalContext()
const [providerOptionList, setProviderOptionList] = useState<DefaultOptionType[]>()
const location = useLocation()
const currentUrl = location.pathname
useImperativeHandle(ref, () => ({
save:onFinish
}));
useEffect(()=>{
if(currentUrl.indexOf('aiInside') !== -1){
setShowAI(true)
}
},[currentUrl])
const getProviderOptionList = ()=>{
setProviderOptionList([])
fetchData<BasicResponse<{ providers: SimpleAiProviderItem[] }>>('simple/ai/providers',{method:'GET',eoTransformKeys:[]}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
const configuredProvider = data.providers?.filter(x=>x.configured)?.map((x:SimpleAiProviderItem)=>{return {...x,
label: x.name, value:x.id
}})
setProviderOptionList(configuredProvider)
if(!serviceId && configuredProvider.length > 0){
form.setFieldsValue({provider: configuredProvider[0]?.id})
}
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
const beforeUpload = async (file: RcFile) => {
if (!['image/png', 'image/jpeg', 'image/svg+xml'].includes(file.type)) {
alert($t('只允许上传PNG、JPG或SVG格式的图片'));
@@ -72,6 +105,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
};
const handleChange: UploadProps['onChange'] = (info: UploadChangeParam<UploadFile>) => {
if (info.file.status === 'uploading') {
setUploadLoading(true);
@@ -104,6 +138,10 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
}})||[])
setServiceClassifyOptionList(data.catalogues)
if(form.getFieldValue('catalogue') === undefined&&data.catalogues.length){
form.setFieldValue('catalogue',data.catalogues[0].id);
}
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
@@ -113,7 +151,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
// 获取表单默认值
const getSystemInfo = () => {
fetchData<BasicResponse<{ service: SystemConfigFieldType }>>('service/info',{method:'GET',eoParams:{team:teamId, service:serviceId},eoTransformKeys:['team_id','service_type']}).then(response=>{
fetchData<BasicResponse<{ service: SystemConfigFieldType }>>('service/info',{method:'GET',eoParams:{team:teamId, service:serviceId},eoTransformKeys:['team_id','service_type','approval_type','service_kind']}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setTimeout(()=>{
@@ -122,6 +160,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
team:data.service.team.id,
catalogue:data.service.catalogue?.id,
tags:data.service.tags?.map((x:EntityItem)=>x.name),
provider:data.service.provider?.id,
logoFile:[
{
uid: '-1', // 文件唯一标识
@@ -142,7 +181,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
const onFinish:()=>Promise<boolean|string> = () => {
return form.validateFields().then((value)=>{
return fetchData<BasicResponse<{service:{id:string}}>>(serviceId === undefined? 'team/service':'service/info',{method:serviceId === undefined? 'POST' : 'PUT',eoParams: {...(serviceId === undefined ? {team:value.team} :{service:serviceId,team:teamId})},eoBody:({...value,prefix:value.prefix?.trim()}), eoTransformKeys:['serviceType']},).then(response=>{
return fetchData<BasicResponse<{service:{id:string}}>>(serviceId === undefined? 'team/service':'service/info',{method:serviceId === undefined? 'POST' : 'PUT',eoParams: {...(serviceId === undefined ? {team:value.team} :{service:serviceId,team:teamId})},eoBody:({...value,prefix:value.prefix?.trim()}), eoTransformKeys:['serviceType','approvalType','serviceKind']},).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
@@ -168,6 +207,9 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
setTeamOptionList(data.teams?.map((x:MemberItem)=>{return {...x,
label:x.name, value:x.id
}}))
if(form.getFieldValue('team') === undefined&&data.teams?.length){
form.setFieldValue('team',data.teams[0].id);
}
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
@@ -186,6 +228,11 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
})
}
useEffect(()=>{
aiConfigFlushed && getProviderOptionList()
},[aiConfigFlushed])
useEffect(() => {
if(accessInit){
getTeamOptionList()
@@ -194,6 +241,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
getTeamOptionList()
})
}
getProviderOptionList()
getTagAndServiceClassifyList()
if (serviceId !== undefined) {
setOnEdit(true);
@@ -205,12 +253,15 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
{
title: $t('设置')
}])
} else {
setOnEdit(false);
form.setFieldValue('id',uuidv4());
const id = uuidv4()
form.setFieldValue('id',id);
form.setFieldValue('serviceKind',serviceTypeOptions[0].value)
form.setFieldValue('prefix',`${id.split('-')[0]}/`)
form.setFieldValue('team',teamId);
form.setFieldValue('serviceType','inner');
form.setFieldValue('serviceType','public');
form.setFieldValue('approvalType','auto');
}
return (form.setFieldsValue({}))
}, [serviceId]);
@@ -234,7 +285,9 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
})
}
const visualizationOptions = useMemo(()=>SERVICE_VISUALIZATION_OPTIONS.map((x)=>({...x, label:$t(x.label)})),[state.language])
const serviceTypeOptions = useMemo(()=>SERVICE_KIND_OPTIONS.map((x)=>({...x, label:$t(x.label)})),[state.language]);
// const visualizationOptions = useMemo(()=>SERVICE_VISUALIZATION_OPTIONS.map((x)=>({...x, label:$t(x.label)})),[state.language])
const approvalOptions = useMemo(()=>SERVICE_APPROVAL_OPTIONS.map((x)=>({...x, label:$t(x.label)})),[state.language])
return (
<>
@@ -259,25 +312,90 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
</Form.Item>
<Form.Item<SystemConfigFieldType>
label={$t("服务ID")}
label={$t("服务 ID")}
name="id"
rules={[{ required: true ,whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" disabled={onEdit} placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
{!onEdit&&
<Form.Item<SystemConfigFieldType>
label={$t("服务类型")}
name="serviceKind"
rules={[{required: true}]}
>
<Select className="w-INPUT_NORMAL" disabled={onEdit} placeholder={$t(PLACEHOLDER.input)} options={serviceTypeOptions} onChange={(value)=>setShowAI(value === 'ai')}>
</Select>
</Form.Item>
}
{showAI && <Form.Item<AiServiceConfigFieldType>
label={$t("默认 AI 供应商")}
name="provider"
rules={[{ required: true }]}
extra={serviceId ? $t('创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API') : ''}
>{
(providerOptionList && providerOptionList.length >0 ) ? <Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} options={providerOptionList} >
</Select> : <p>{$t("未配置任何 AI 模型供应商,")}<a href="/aisetting" target="_blank" onClick={()=>setAiConfigFlushed(false)}>{$t('立即配置')}</a></p>
}
</Form.Item>}
<Form.Item<SystemConfigFieldType>
label={$t("API 调用前缀")}
name="prefix"
extra={$t("选填,作为服务内所有API的前缀,比如host/{service_name}/{api_path},一旦保存无法修改")}
rules={[
extra={$t("作为服务内所有API的前缀,比如host/{service_name}/{api_path},一旦保存无法修改")}
rules={[{ required: true ,whitespace:true },
{
validator: validateUrlSlash,
}]}
>
<Input prefix={onEdit ? '' : '/'} className="w-INPUT_NORMAL" disabled={onEdit} placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
{!onEdit && <Form.Item<SystemConfigFieldType>
label={$t("所属团队")}
name="team"
rules={[{ required: true }]}
>
<Select className="w-INPUT_NORMAL" disabled={onEdit} placeholder={$t(PLACEHOLDER.input)} options={teamOptionList} >
</Select>
</Form.Item>}
<Form.Item<SystemConfigFieldType>
label={$t("订阅审批")}
name="approvalType"
rules={[{required: true}]}
>
<Radio.Group className="flex flex-col" options={approvalOptions} />
</Form.Item>
{/* <Form.Item<SystemConfigFieldType>
label={$t("服务类型")}
name="serviceType"
rules={[{required: true}]}
>
<Radio.Group className="flex flex-col" options={visualizationOptions} onChange={(e)=>{setShowClassify(e.target.value === 'public')}} />
</Form.Item> */}
{showClassify &&
<Form.Item<SystemConfigFieldType>
label={$t("所属服务分类")}
name="catalogue"
extra={$t("设置服务展示在服务市场中的哪个分类下")}
rules={[{required: true}]}
>
<TreeSelect
className="w-INPUT_NORMAL"
fieldNames={{label:'name',value:'id',children:'children'}}
showSearch
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
placeholder={$t(PLACEHOLDER.select)}
allowClear
treeDefaultExpandAll
treeData={serviceClassifyOptionList}
/>
</Form.Item>
}
<Form.Item<SystemConfigFieldType>
label={$t("图标")}
name="logoFile"
@@ -313,15 +431,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
>
</Form.Item>
{!onEdit && <Form.Item<SystemConfigFieldType>
label={$t("所属团队")}
name="team"
rules={[{ required: true }]}
>
<Select className="w-INPUT_NORMAL" disabled={onEdit} placeholder={$t(PLACEHOLDER.input)} options={teamOptionList} >
</Select>
</Form.Item>}
<Form.Item<SystemConfigFieldType>
label={$t("标签")}
@@ -334,34 +444,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
options={tagOptionList}>
</Select>
</Form.Item>
<Form.Item<SystemConfigFieldType>
label={$t("服务类型")}
name="serviceType"
rules={[{required: true}]}
>
<Radio.Group className="flex flex-col" options={visualizationOptions} onChange={(e)=>{setShowClassify(e.target.value === 'public')}} />
</Form.Item>
{showClassify &&
<Form.Item<SystemConfigFieldType>
label={$t("所属服务分类")}
name="catalogue"
extra={$t("设置服务展示在服务市场中的哪个分类下")}
rules={[{required: true}]}
>
<TreeSelect
className="w-INPUT_NORMAL"
fieldNames={{label:'name',value:'id',children:'children'}}
showSearch
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
placeholder={$t(PLACEHOLDER.select)}
allowClear
treeDefaultExpandAll
treeData={serviceClassifyOptionList}
/>
</Form.Item>
}
{onEdit && <>
<Row className="mb-[10px]"
// wrapperCol={{ offset: 5, span: 19 }}
@@ -19,7 +19,7 @@ const APP_MODE = import.meta.env.VITE_APP_MODE;
const SystemInsidePage:FC = ()=> {
const { message } = App.useApp()
const { teamId,serviceId,apiId} = useParams<RouterParams>();
const { teamId,serviceId,apiId,routeId} = useParams<RouterParams>();
const location = useLocation()
const currentUrl = location.pathname
const {fetchData} = useFetch()
@@ -27,6 +27,7 @@ const SystemInsidePage:FC = ()=> {
const { accessData,checkPermission,accessInit,state} = useGlobalContext()
const [activeMenu, setActiveMenu] = useState<string>()
const navigateTo = useNavigate()
const [showMenu, setShowMenu] = useState<boolean>(false)
const getSystemInfo = ()=>{
fetchData<BasicResponse<{ service:SystemConfigFieldType }>>('service/info',{method:'GET',eoParams:{team:teamId, service:serviceId}}).then(response=>{
@@ -58,8 +59,8 @@ const SystemInsidePage:FC = ()=> {
const SYSTEM_PAGE_MENU_ITEMS = useMemo(()=>[
getItem($t('服务'), 'assets', null,
[
getItem(<Link to="./api">{$t('API')}</Link>, 'api',undefined,undefined,undefined,'team.service.api_doc.view'),
getItem(<Link to="./route">{$t('路由')}</Link>, 'route',undefined,undefined,undefined,'team.service.router.view'),
getItem(<Link to="./route">{$t('API 路由')}</Link>, 'route',undefined,undefined,undefined,'team.service.router.view'),
getItem(<Link to="./api">{$t('API 文档')}</Link>, 'api',undefined,undefined,undefined,'team.service.api_doc.view'),
getItem(<Link to="./upstream">{$t('上游')}</Link>, 'upstream',undefined,undefined,undefined,'team.service.upstream.view'),
getItem(<Link to="./document">{$t('使用说明')}</Link>, 'document',undefined,undefined,undefined,''),
getItem(<Link to="./publish">{$t('发布')}</Link>, 'publish',undefined,undefined,undefined,'team.service.release.view'),
@@ -94,7 +95,7 @@ const SystemInsidePage:FC = ()=> {
}
const filteredMenu = filterMenu(SYSTEM_PAGE_MENU_ITEMS as MenuItemGroupType<MenuItemType>[])
setActiveMenu((pre)=>{
return pre ?? 'api'
return pre ?? 'route'
})
return filteredMenu || []
},[accessData,accessInit, SYSTEM_PAGE_MENU_ITEMS])
@@ -104,12 +105,13 @@ const SystemInsidePage:FC = ()=> {
};
useEffect(() => {
setShowMenu(!routeId && !currentUrl.includes('route/create'))
if(apiId !== undefined){
setActiveMenu('api')
}else if(serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]){
setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1])
}else{
setActiveMenu('api')
setActiveMenu('route')
}
}, [currentUrl]);
@@ -130,7 +132,7 @@ const SystemInsidePage:FC = ()=> {
}, [serviceId]);
return (
<>
<>{showMenu ?
<InsidePage pageTitle={systemInfo?.name || '-'}
tagList={[{label:
<Paragraph className="mb-0" copyable={serviceId ? { text: serviceId } : false}>{$t('服务 ID')}{serviceId || '-'}</Paragraph>
@@ -149,7 +151,7 @@ const SystemInsidePage:FC = ()=> {
<Outlet/>
</div>
</div>
</InsidePage>
</InsidePage>: <Outlet/> }
</>
)
@@ -9,12 +9,13 @@ import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx
import {useFetch} from "@common/hooks/http.ts";
import { SimpleTeamItem ,SimpleMemberItem} from "@common/const/type.ts";
import { SystemConfigHandle, SystemTableListItem } from "../../const/system/type.ts";
import { SYSTEM_TABLE_COLUMNS } from "../../const/system/const.tsx";
import { SERVICE_KIND_OPTIONS, SYSTEM_TABLE_COLUMNS } from "../../const/system/const.tsx";
import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter.tsx";
import SystemConfig from "./SystemConfig.tsx";
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
import { $t } from "@common/locales/index.ts";
import Joyride from "react-joyride";
import InsidePage from "@common/components/aoplatform/InsidePage.tsx";
const SystemList:FC = ()=>{
const navigate = useNavigate();
@@ -117,12 +118,21 @@ const SystemList:FC = ()=>{
const columns = useMemo(()=>{
const res = SYSTEM_TABLE_COLUMNS.map(x=>{
if(x.filters &&((x.dataIndex as string[])?.indexOf('master') !== -1 ) ){
const dataIndex=(x.dataIndex as string[]);
if(x.filters &&dataIndex?.indexOf('master') !== -1 ){
x.valueEnum = memberValueEnum
}
if(x.filters &&((x.dataIndex as string[])?.indexOf('team') !== -1 ) ){
if(x.filters &&dataIndex?.indexOf('team') !== -1 ){
x.valueEnum = teamList
}
if((x.dataIndex as string)==='service_kind'){
x.valueEnum={};
SERVICE_KIND_OPTIONS
.forEach(option => {
(x.valueEnum as any)[option.value] = { text: $t(option.label) };
});
}
return {...x,title:typeof x.title === 'string' ? $t(x.title as string) : x.title}})
return res
@@ -141,8 +151,13 @@ const SystemList:FC = ()=>{
];
return (
<div className="h-full w-full pr-PAGE_INSIDE_X pb-PAGE_INSIDE_B">
{/* <Joyride steps={steps} run={true} /> */}
<InsidePage
pageTitle={$t('服务')}
description={'APIPark '+$t("服务提供了高性能 API 网关,并且可以无缝接入多种大型 AI 模型,并将这些 AI 能力打包成 API 进行调用,从而大幅简化了 AI 模型的使用门槛。同时,我们的平台提供了完善的 API 管理功能,支持 API 的创建、监控、访问控制等,保障开发者可以高效、安全地开发和管理 API 服务。")}
showBorder={false}
contentClassName=" pr-PAGE_INSIDE_X pb-PAGE_INSIDE_B"
>
<PageList
id="global_system"
ref={pageListRef}
@@ -161,12 +176,12 @@ const SystemList:FC = ()=>{
onSearchWordChange={(e) => {
setTableSearchWord(e.target.value)
}}
onRowClick={(row:SystemTableListItem)=>navigate(`/service/${row.team.id}/inside/${row.id}`)}
onRowClick={(row:SystemTableListItem)=>navigate(`/service/${row.team.id}/${row.service_kind==='ai'?'aiInside':'inside'}/${row.id}`)}
/>
<DrawerWithFooter title={$t("添加服务")} open={open} onClose={onClose} onSubmit={()=>drawerFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})} >
<SystemConfig ref={drawerFormRef} />
</DrawerWithFooter>
</div>
</InsidePage>
)
}
@@ -60,7 +60,7 @@ const SystemInsideApiDocument = forwardRef<SystemInsideApiDocumentHandle,SystemI
uploadFile(file)
return false;
}}>
<Button type="primary">{$t(type === 'new' ? '上传文件' :'替换文件')}</Button>
<Button type="primary">{$t(type === 'new' ? '上传 OpenAPI 文档 (.json/.yaml)' :'替换 OpenAPI 文档 (.json/.yaml)')}</Button>
</Upload>
</WithPermission>
)
@@ -113,7 +113,7 @@ const SystemInsideApiDocument = forwardRef<SystemInsideApiDocumentHandle,SystemI
<div className="flex items-center gap-btnbase justify-end pb-btnbase pr-btnbase mr-PAGE_INSIDE_X">
<UploadBtn type="edit" updated={updated}/>
<WithPermission access="team.service.api_doc.edit">
<Button type="primary" onClick={()=>setShowEditor(true)}>{$t('打开编辑器')}</Button>
<Button type="primary" onClick={()=>setShowEditor(true)}>{$t('打开 OpenAPI YAML 编辑器')}</Button>
</WithPermission>
</div>
<div className="flex-1 overflow-auto pr-PAGE_INSIDE_X">
@@ -137,7 +137,7 @@ const SystemInsideApiDocument = forwardRef<SystemInsideApiDocumentHandle,SystemI
<div className="flex items-center gap-btnbase justify-center">
<UploadBtn type="new" updated={updated}/>
<WithPermission access="team.service.api_doc.edit">
<Button type="primary" onClick={()=>setShowEditor(true)}>{$t('打开编辑器')}</Button>
<Button type="primary" onClick={()=>setShowEditor(true)}>{$t('打开 OpenAPI YAML 编辑器')}</Button>
</WithPermission>
</div>
</Empty>}
@@ -4,7 +4,7 @@ import { forwardRef, useEffect, useImperativeHandle, useMemo } from "react"
import EditableTableWithModal from "@common/components/aoplatform/EditableTableWithModal";
import { PROXY_HEADER_CONFIG, UPSTREAM_PROXY_HEADER_TYPE_OPTIONS } from "../../../const/system/const";
import { SystemApiProxyType, ProxyHeaderItem, SystemInsideApiProxyHandle, SystemInsideApiProxyProps } from "../../../const/system/type";
import { PLACEHOLDER, VALIDATE_MESSAGE } from "@common/const/const";
import { PLACEHOLDER, } from "@common/const/const";
import { $t } from "@common/locales";
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
@@ -1,4 +1,4 @@
import {App, Col, Form, Input, Row, Select, Spin, Switch} from "antd";
import {App, Button, Col, Form, Input, Row, Select, Spin, Switch} from "antd";
import {forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react";
import EditableTableWithModal from "@common/components/aoplatform/EditableTableWithModal.tsx";
import styles from "./SystemInsideApi.module.css"
@@ -12,23 +12,33 @@ import { $t } from "@common/locales/index.ts";
import SystemInsideApiProxy from "@core/pages/system/api/SystemInsideApiProxy.tsx";
import { LoadingOutlined } from "@ant-design/icons";
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx";
import { useNavigate, useParams } from "react-router-dom";
import { useSystemContext } from "@core/contexts/SystemContext.tsx";
import InsidePage from "@common/components/aoplatform/InsidePage.tsx";
const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle,SystemInsideRouterCreateProps>((props, ref) => {
const { message } = App.useApp()
const {type, entity, serviceId,teamId, modalApiPrefix:apiPrefix, modalPrefixForce:prefixForce} = props
const {serviceId, teamId, routeId} = useParams<RouterParams>()
const [form] = Form.useForm();
const {fetchData} = useFetch()
const [loading, setLoading] = useState<boolean>(false)
const proxyRef = useRef<SystemInsideApiProxyHandle>(null)
const { state } = useGlobalContext()
const {apiPrefix, prefixForce} = useSystemContext()
const navigator = useNavigate()
const onFinish = ()=>{
return Promise.all([proxyRef.current?.validate?.(), form.validateFields()]).then(([,formValue])=>{
const body = {...formValue,path:formValue.path.trim(),proxy:{...formValue.proxy,path:formValue.proxy.path ? (formValue.proxy.path.startsWith('/')? formValue.proxy.path: '/'+ formValue.proxy.path) : undefined}}
return fetchData<BasicResponse<null>>('service/router',{method: type === 'add' ? 'POST' : 'PUT',eoBody:(body), eoParams: {service:serviceId,team:teamId, ...(type === 'edit' ? {router:entity?.id}: {})},eoTransformKeys:['matchType','disable']}).then(response=>{
const body = {...formValue,
path: prefixForce? `${apiPrefix}/${formValue.path.trim()}` : formValue.path.trim(),
proxy:{...formValue.proxy,path:formValue.proxy.path ? (formValue.proxy.path.startsWith('/')? formValue.proxy.path: '/'+ formValue.proxy.path) : undefined}}
return fetchData<BasicResponse<null>>('service/router',{
method: routeId ? 'PUT' :'POST' ,eoBody:(body), eoParams: {service:serviceId,team:teamId, router:routeId },eoTransformKeys:['matchType','disable']}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
navigator(`/service/${teamId}/inside/${serviceId}/route`)
return Promise.resolve(true)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
@@ -41,7 +51,7 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle,Syste
const copy: ()=>Promise<boolean | string> = ()=>{
return new Promise((resolve, reject)=>{
return form.validateFields().then((value)=>{
fetchData<BasicResponse<{api:SystemApiProxyFieldType}>>('service/api/copy',{method:'POST',eoParams:{service:serviceId,team:teamId, api:entity!.id},eoBody:({...value,path:value.path.trim()})}).then(response=>{
fetchData<BasicResponse<{api:SystemApiProxyFieldType}>>('service/api/copy',{method:'POST',eoParams:{service:serviceId,team:teamId, api:routeId},eoBody:({...value,path:value.path.trim()})}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
@@ -63,7 +73,7 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle,Syste
const getRouterConfig = ()=>{
setLoading(true)
fetchData<BasicResponse<{router:SystemApiProxyFieldType}>>('service/router/detail',{method:'GET',eoParams:{service:serviceId,team:teamId, router:entity!.id}, eoTransformKeys:['create_time','update_time','match_type','upstream_id','opt_type']}).then(response=>{
fetchData<BasicResponse<{router:SystemApiProxyFieldType}>>('service/router/detail',{method:'GET',eoParams:{service:serviceId,team:teamId, router:routeId}, eoTransformKeys:['create_time','update_time','match_type','upstream_id','opt_type']}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
const {disable, protocols, path, methods, description, match, proxy} = data.router
@@ -77,28 +87,15 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle,Syste
}
useEffect(() => {
switch(type){
case 'edit':
if(routeId){
getRouterConfig()
break;
case 'add':
form.setFieldValue('prefix',apiPrefix)
form.setFieldValue(['proxy','timeout'],10000)
form.setFieldValue(['proxy','retry'],0)
form.setFieldValue('protocols',['HTTP','HTTPS'])
break;
case 'copy':
// form.setFieldsValue({
// ...entity,
// name:`${$t('副本')}-${entity!.name}`,
// ...(prefixForce?
// {prefix:apiPrefix,path: entity!.path.substring(apiPrefix?.length|| 0)}:
// {}),
// proxy:{timeout:10000, retry:0, ...entity?.proxy}
// });
break;
}else{
form.setFieldValue('prefix',apiPrefix)
form.setFieldValue(['proxy','timeout'],10000)
form.setFieldValue(['proxy','retry'],0)
form.setFieldValue('protocols',['HTTP','HTTPS'])
}
return (form.setFieldsValue({}))
return (form.setFieldsValue({}))
}, []);
@@ -118,7 +115,19 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle,Syste
})
}, [state.language])
return (<div className="h-full w-full">
return (
<InsidePage pageTitle={ $t('API 路由设置')|| '-'}
showBorder={false}
scrollPage={false}
className="overflow-y-auto"
backUrl={`/service/${teamId}/inside/${serviceId}/route`}
customBtn={
<div className="flex gap-btnbase items-center">
<Button type="primary" onClick={onFinish}>
{$t('保存')}
</Button>
</div>
}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className=''>
<Form
layout='vertical'
@@ -190,20 +199,18 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle,Syste
</Form.Item>
{/* } */}
{ type !== 'copy' &&<>
<Row className="mb-btnybase mt-[40px]"><Col ><span className="font-bold mr-[13px]">{$t('转发规则设置')} </span></Col></Row>
<Form.Item<SystemApiProxyFieldType>
className="mb-0 bg-transparent border-none p-0"
name="proxy"
>
<SystemInsideApiProxy type={type} serviceId={serviceId!} teamId={teamId!} ref={proxyRef} />
<SystemInsideApiProxy type={routeId ? 'edit' : 'add'} ref={proxyRef} />
</Form.Item>
</>}
</div>
</Form>
</Spin>
</div>
</InsidePage>
)
})
export default SystemInsideRouterCreate
@@ -1,21 +1,18 @@
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx"
import {ActionType} from "@ant-design/pro-components";
import {FC, useEffect, useMemo, useRef, useState} from "react";
import {Link, useParams} from "react-router-dom";
import {Link, useNavigate, useParams} from "react-router-dom";
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
import {App, Divider} from "antd";
import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
import { SimpleMemberItem} from '@common/const/type.ts'
import {useFetch} from "@common/hooks/http.ts";
import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx";
import SystemInsideRouterCreate from "./SystemInsideRouterCreate.tsx";
import {useSystemContext} from "../../../contexts/SystemContext.tsx";
import { SYSTEM_API_TABLE_COLUMNS } from "../../../const/system/const.tsx";
import {SystemApiTableListItem, SystemInsideRouterCreateHandle } from "../../../const/system/type.ts";
import {SystemApiTableListItem } from "../../../const/system/type.ts";
import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx";
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
import { checkAccess } from "@common/utils/permission.ts";
import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter.tsx";
import { $t } from "@common/locales/index.ts";
const SystemInsideRouterList:FC = ()=>{
@@ -26,15 +23,10 @@ const SystemInsideRouterList:FC = ()=>{
const [tableHttpReload, setTableHttpReload] = useState(true);
const {fetchData} = useFetch()
const pageListRef = useRef<ActionType>(null);
const {apiPrefix, prefixForce} = useSystemContext()
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
const {accessData,state} = useGlobalContext()
const [drawerType,setDrawerType]= useState<'add'|'edit'|'view'|'upstream'|undefined>()
const [open, setOpen] = useState(false);
const drawerAddFormRef = useRef<SystemInsideRouterCreateHandle>(null)
const {serviceId, teamId} = useParams<RouterParams>()
const [curApi, setCurApi] = useState<SystemApiTableListItem>()
const navigator = useNavigate()
const getRoutesList = (): Promise<{ data: SystemApiTableListItem[], success: boolean }>=> {
if(!tableHttpReload){
@@ -113,7 +105,7 @@ const SystemInsideRouterList:FC = ()=>{
fixed:'right',
valueType: 'option',
render: (_: React.ReactNode, entity: SystemApiTableListItem) => [
<TableBtnWithPermission access="team.service.router.edit" key="edit" btnType="edit" onClick={()=>{openDrawer('edit',entity)}} btnTitle="编辑"/>,
<TableBtnWithPermission access="team.service.router.edit" key="edit" btnType="edit" onClick={()=>{navigator(`/service/${teamId}/inside/${serviceId}/route/${entity.id}`)}} btnTitle="编辑"/>,
<Divider type="vertical" className="mx-0" key="div3"/>,
<TableBtnWithPermission access="team.service.router.delete" key="delete" btnType="delete" onClick={()=>{openModal('delete',entity)}} btnTitle="删除"/>,
],
@@ -135,12 +127,12 @@ const SystemInsideRouterList:FC = ()=>{
}
}
const openDrawer = (type:'add'|'edit'|'view',entity?:SystemApiTableListItem)=>{
setCurApi(entity)
setDrawerType(type)
}
// const openDrawer = (type:'add'|'edit'|'view',entity?:SystemApiTableListItem)=>{
// setCurApi(entity)
// setDrawerType(type)
// }
useEffect(()=>{drawerType !== undefined ? setOpen(true):setOpen(false)},[drawerType])
// useEffect(()=>{drawerType !== undefined ? setOpen(true):setOpen(false)},[drawerType])
useEffect(() => {
setBreadcrumb([
@@ -155,10 +147,10 @@ const SystemInsideRouterList:FC = ()=>{
manualReloadTable()
}, [serviceId]);
const onClose = () => {
setDrawerType(undefined);
setCurApi(undefined)
};
// const onClose = () => {
// setDrawerType(undefined);
// setCurApi(undefined)
// };
const columns = useMemo(()=>{
return [...SYSTEM_API_TABLE_COLUMNS].map(x=>{
@@ -179,17 +171,17 @@ const SystemInsideRouterList:FC = ()=>{
return {...x,title:typeof x.title === 'string' ? $t(x.title as string) : x.title}})
},[memberValueEnum,state.language])
const handlerSubmit:() => Promise<string | boolean>|undefined= ()=>{
switch(drawerType){
case 'add':{
return drawerAddFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})
}
case 'edit':{
return drawerAddFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})
}
default:return undefined
}
}
// const handlerSubmit:() => Promise<string | boolean>|undefined= ()=>{
// switch(drawerType){
// case 'add':{
// return drawerAddFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})
// }
// case 'edit':{
// return drawerAddFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})
// }
// default:return undefined
// }
// }
return (
<>
@@ -201,7 +193,8 @@ const SystemInsideRouterList:FC = ()=>{
dataSource={tableListDataSource}
addNewBtnTitle={$t('添加路由')}
searchPlaceholder={$t('输入 URL 查找路由')}
onAddNewBtnClick={()=>{openDrawer('add')}}
// onAddNewBtnClick={()=>{openDrawer('add')}}
onAddNewBtnClick={()=>{navigator(`/service/${teamId}/inside/${serviceId}/route/create`)}}
addNewBtnAccess="team.service.router.add"
tableClickAccess="team.service.router.view"
manualReloadTable={manualReloadTable}
@@ -209,10 +202,10 @@ const SystemInsideRouterList:FC = ()=>{
onChange={() => {
setTableHttpReload(false)
}}
onRowClick={(row:SystemApiTableListItem)=>openDrawer('edit',row)}
onRowClick={(row:SystemApiTableListItem)=>navigator(`/service/${teamId}/inside/${serviceId}/route/${row.id}`)}
tableClass="mr-PAGE_INSIDE_X "
/>
<DrawerWithFooter
{/* <DrawerWithFooter
title={drawerType === 'add' ? $t("添加路由"):$t("路由详情")}
open={open}
onClose={onClose}
@@ -220,7 +213,7 @@ const SystemInsideRouterList:FC = ()=>{
showOkBtn={drawerType !== 'view'}
>
<SystemInsideRouterCreate ref={drawerAddFormRef} type={drawerType as 'add'|'edit'|'copy'} entity={drawerType === 'edit' ? curApi : undefined} modalApiPrefix={apiPrefix} serviceId={serviceId!} teamId={teamId!} modalPrefixForce={prefixForce}/>
</DrawerWithFooter>
</DrawerWithFooter> */}
</>
)
@@ -1,4 +1,4 @@
import {FC, useMemo} from 'react';
import {FC, useEffect, useMemo} from 'react';
import ECharts,{EChartsOption} from 'echarts-for-react';
import { changeNumberUnit } from '../utils/dashboard';
import { $t } from '@common/locales';
@@ -91,7 +91,7 @@ const MonitorPieGraph: FC<PieGraphProps> = ({ className,title, pieData, labelNam
data: transferData(pieData),
},
],
}),[state.language])
}),[state.language,pieData])
return (
<div className={`${className} min-w-[570px] p-[16px] relative text-DESC_TEXT overflow-x-auto rounded`}>
@@ -79,7 +79,7 @@ const MonitorTotalPage = (props:MonitorTotalPageProps) => {
const [fullScreen, setFullScreen] = useState<boolean>(false)
const [recordQuery, setRecordQuery] = useState<SearchBody&{timeButton:''|'hour'|'day'|'threeDays'|'sevenDays'}>()
const [queryBtnLoading, setQueryBtnLoading] = useState<boolean>(false)
const [totalEmpty, setTotalEmpty] = useState<boolean>(true)
const [totalEmpty, setTotalEmpty] = useState<boolean>(false)
const [requestStatus, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
@@ -92,7 +92,7 @@ const MonitorTotalPage = (props:MonitorTotalPageProps) => {
}, []);
const getMonitorData = () => {
setTotalEmpty(true)
// setTotalEmpty(true)
dispatch({ type: ACTIONS.RESET });
// ...根据时间和集群获取监控数据...
let query = queryData
@@ -126,7 +126,7 @@ const MonitorTotalPage = (props:MonitorTotalPageProps) => {
// this.proxyPieRef?.changePieChart()
setRequestSucRate(data.requestSummary.total === 0 ? '0%' : (data.requestSummary.success * 100 / data.requestSummary.total).toFixed(2) + '%')
setProxySucRate(data.proxySummary.total === 0 ? '0%' : (data.proxySummary.success * 100 / data.proxySummary.total).toFixed(2) + '%')
setTotalEmpty(data.requestSummary.total === 0 && data.proxySummary.total === 0)
// setTotalEmpty(data.requestSummary.total === 0 && data.proxySummary.total === 0)
}else{
setPieError(true)
message.error(msg || $t(RESPONSE_TIPS.dataError))
@@ -1,12 +1,10 @@
import { useNavigate, useParams } from "react-router-dom"
import { RouterParams } from "@core/components/aoplatform/RenderRoutes"
import MonitorTotalPage from "@dashboard/component/MonitorTotalPage"
import { BasicResponse } from "@common/const/const"
import { InvokeData, MessageData, MonitorApiData, MonitorSubscriberData, PieData, SearchBody } from "@dashboard/const/type"
import { useFetch } from "@common/hooks/http"
import { objectToSearchParameters } from "@common/utils/router"
import { useEffect } from "react"
export default function DashboardTotal() {
const {fetchData } = useFetch()
const navigateTo = useNavigate()
-2
View File
@@ -28,5 +28,3 @@
width:22px;
}
}
@@ -46,3 +46,7 @@ export const SERVICE_HUB_TABLE_COLUMNS: PageProColumns<ServiceHubTableListItem>[
];
export const approvalTypeTranslate = {
'auto':'无需审批',
'manual':'需要审批'
}
@@ -15,6 +15,8 @@ export type ServiceBasicInfoType = {
updateTime:string
version:string
logo?:string
invokeAddress:string
approvalType:'auto'|'manual'
}
export type ServiceDetailType = {
@@ -63,7 +65,7 @@ export type ServiceHubTableListItem = {
export type ApplyServiceProps = {
entity:ServiceHubTableListItem & {app:EntityItem}
entity:ServiceBasicInfoType & EntityItem
mySystemOptionList:DefaultOptionType[]
reApply?:boolean
}
@@ -18,7 +18,6 @@ export default function ApiTestGroup({apiInfoList,selectedApiId }:ApiTestGroupTy
const [selectedApi,setSelectedApi] = useState<string[]>([selectedApiId])
const [selectedApiInfo, setSelectedApiInfo] = useState<ApiDetail>()
const onSearchWordChange = (e:unknown)=>{
//console.log(e)
}
useEffect(()=>{
@@ -63,14 +63,14 @@ export const ApplyServiceModal = forwardRef<ApplyServiceHandle,ApplyServiceProps
>
<Select className="w-INPUT_NORMAL" disabled={reApply} placeholder={$t("搜索或选择应用")} mode="multiple" options={mySystemOptionList?.filter((x)=>x.value !== entity.id)}/>
</Form.Item>
{ entity.approvalType === 'manual' &&
<Form.Item
label={$t("申请理由")}
name="reason"
>
<Input.TextArea className="w-INPUT_NORMAL" placeholder=""/>
</Form.Item>
}
</Form>
</WithPermission>)
})
@@ -15,6 +15,7 @@ import { SimpleSystemItem } from "@core/const/system/type.ts";
import { Icon } from "@iconify/react/dist/iconify.js";
import DOMPurify from 'dompurify';
import { $t } from "@common/locales/index.ts";
import { approvalTypeTranslate } from "@market/const/serviceHub/const.tsx";
const ServiceHubDetail = ()=>{
@@ -34,7 +35,7 @@ const ServiceHubDetail = ()=>{
const navigate = useNavigate();
const getServiceBasicInfo = ()=>{
fetchData<BasicResponse<{service:ServiceDetailType}>>('catalogue/service',{method:'GET',eoParams:{service:serviceId}, eoTransformKeys:['app_num','api_num','update_time','api_doc']}).then(response=>{
fetchData<BasicResponse<{service:ServiceDetailType}>>('catalogue/service',{method:'GET',eoParams:{service:serviceId}, eoTransformKeys:['app_num','api_num','update_time','api_doc','invoke_address','approval_type']}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setService(data.service)
@@ -137,7 +138,8 @@ const ServiceHubDetail = ()=>{
<p className="text-[14px] h-[20px] leading-[20px] truncate font-bold flex items-center gap-[4px]">{serviceName}
</p>
<div className="mt-[10px] flex flex-col gap-btnrbase font-normal">
{serviceDesc || '-'}
<p>{serviceDesc || '-'}</p>
<p className="flex items-center gap-[4px]"><Icon icon="ic:baseline-link" width="18" height="18" /><span className="font-bold">{$t('Basic URL')}</span>: {serviceBasicInfo?.invokeAddress || '-'}</p>
<div>
<Button type="primary" onClick={()=>openModal('apply')}>{$t('申请')}</Button>
</div>
@@ -156,6 +158,7 @@ const ServiceHubDetail = ()=>{
<Descriptions title={$t("服务信息")} column={1} size={'small'}>
<Descriptions.Item label={$t("接入应用")}>{serviceBasicInfo?.appNum ?? '-'}</Descriptions.Item>
<Descriptions.Item label={$t("供应方")}>{serviceBasicInfo?.team?.name || '-'}</Descriptions.Item>
<Descriptions.Item label={$t("申请审批")}>{serviceBasicInfo?.approvalType ? (approvalTypeTranslate[serviceBasicInfo?.approvalType] || '-' ): '-'}</Descriptions.Item>
<Descriptions.Item label={$t("分类")}>{serviceBasicInfo?.catalogue?.name || '-'}</Descriptions.Item>
<Descriptions.Item label={$t("标签")}>{serviceBasicInfo?.tags?.map(x=>x.name)?.join(',') || '-'}</Descriptions.Item>
</Descriptions>
@@ -119,9 +119,9 @@ export const ManagementAuthorityConfig = forwardRef<ManagementAuthorityConfigHan
>
<Select disabled={type === 'edit'} className="w-INPUT_NORMAL" options={[
{label:'Basic',value:'basic'},
{label:'Jwt',value:'jwt'},
{label:'AkSk',value:'aksk'},
{label:'Apikey',value:'apikey'}]} onChange={(e)=>onDriverChange(e)} placeholder={$t(PLACEHOLDER.input)}/>
{label:'JWT',value:'jwt'},
{label:'AK/SK',value:'aksk'},
{label:'API Key',value:'apikey'}]} onChange={(e)=>onDriverChange(e)} placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
<Form.Item
@@ -17,8 +17,8 @@ export const ManagementAuthorityView = ({entity}:ManagementAuthorityViewProps)=>
<div className="my-btnybase">{
detail?.length > 0 && detail.map((k,i)=>(
<Row className="leading-[32px]" key={i}>
<Col className="pr-[8px]" offset={1} span={5}>{$t(k.key)}:</Col>
<Col className="break-all" span={18}>{ k.value || '-'}</Col>
<Col className="pr-[8px]" offset={1} span={6}>{$t(k.key)}:</Col>
<Col className="break-all" span={17}>{ ['永久','否','是'].indexOf(k.value)!== -1 ? $t(k.value) : (k.value || '-')}</Col>
</Row>
))
}
@@ -59,7 +59,6 @@ export default function ManagementInsideAuth(){
}
const openModal =async (type:'view'|'delete'|'add'|'edit',entity?:SystemAuthorityTableListItem)=>{
//console.log(type,entity)
let title:string = ''
let content:string|React.ReactNode = ''
switch (type){
@@ -161,7 +160,7 @@ export default function ManagementInsideAuth(){
const item = authList[index];
return (<Card className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] m-[10px]" classNames={{body:' flex items-center justify-center p-[20px]'}} >
<div className="w-full">
<div className="flex items-center justify-between w-full"><span>{item.name}</span><div><Button type="text" className="bg-[#7371fc20] hover:bg-[#7371fc19] text-theme" onClick={()=>openModal('view',item)}>{$t('查看')}</Button><Dropdown className="ml-btnbase" menu={{items:dropdownMenu(item)}} trigger={['hover']} >
<div className="flex items-start justify-between w-full"><span>{item.name}</span><div><Button type="text" className="bg-[#7371fc20] hover:bg-[#7371fc19] text-theme" onClick={()=>openModal('view',item)}>{$t('查看')}</Button><Dropdown className="ml-btnbase" menu={{items:dropdownMenu(item)}} trigger={['hover']} >
<Button type="text" className="px-[7px]" onClick={(e)=>{ e.stopPropagation();}}><MoreOutlined rotate={90} className="tree-title-more" /></Button>
</Dropdown></div></div>
<div>
@@ -10,9 +10,10 @@ import { useOutletContext, useParams } from "react-router-dom"
import { RouterParams } from "@core/components/aoplatform/RenderRoutes"
import { TenantManagementServiceListItem } from "../../../const/serviceHub/type"
import { ApprovalModalContent } from "./ApprovalModalContent"
import { checkAccess } from "@common/utils/permission"
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
import { $t } from "@common/locales"
import WithPermission from "@common/components/aoplatform/WithPermission"
import { checkAccess } from "@common/utils/permission"
export default function ManagementInsideService(){
const {message, modal} = App.useApp()
@@ -99,10 +100,18 @@ export default function ManagementInsideService(){
},
width:600,
okText:$t('确认'),
okButtonProps:{
disabled : !checkAccess( `team.application.authorization.${type}`, accessData)
},
cancelText:$t('取消'),
okButtonProps:{
disabled : !checkAccess( `team.application.authorization.${type}`, accessData )
},
footer: (_, { OkBtn, CancelBtn }) => (
<>
<CancelBtn />
<WithPermission access={`team.application.authorization.${type}`}>
<OkBtn />
</WithPermission>
</>
),
closable:true,
icon:<></>,
})
@@ -132,11 +141,11 @@ export default function ManagementInsideService(){
}:{
key: 'cancelSub',
label: (
// <WithPermission access="system.organization.member.department.delete" key="deletePermission">
// <WithPermission access="team.application.authorization.delete" showDisabled={true} key="deletePermission">
<Button key="cancelSub" type="text" className="h-[32px] border-none p-0 flex items-center bg-transparent " onClick={()=>openModal('cancelSub',entity)}>
{$t('取消订阅')}
</Button>
// </WithPermission>
// </WithPermission>
),
},
]
@@ -353,14 +353,14 @@ export default function SystemRunning(){
labelCfg: {
style: {
fill: '5B8FF9',
opacity: 0 // 将透明度设置为0,隐藏提示信息,hover 才出现
opacity: 0
}
}
},
defaultCombo: {
labelCfg: {
style: {
fill: '#666', // combo 的文本颜色
fill: '#666'
},
},
},