Merge pull request #66 from APIParkLab/feature/v1.2

Feature/v1.2
This commit is contained in:
Maggie
2024-10-24 14:41:41 +08:00
committed by GitHub
48 changed files with 823 additions and 372 deletions
@@ -253,7 +253,7 @@ const PUBLIC_ROUTES:RouteConfig[] = [
key:uuidv4(),
},
{
path:'tenantManagement',
path:'consumer',
component:<Outlet />,
provider:TenantManagementProvider,
key:uuidv4(),
@@ -48,8 +48,8 @@ const themeToken = {
const TOTAL_MENU_ITEMS:MenuProps['items'] = useMemo(() => [
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>, 'consumer','/consumer',<Icon icon="ic:baseline-apps" 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('API 市场'), 'serviceHub','/serviceHub',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.workspace.api_market.view'),
@@ -17,8 +17,8 @@ export const TranslateWord = ()=>{
{$t('只读成员')}
{$t('服务管理员')}
{$t('服务开发者')}
{$t('应用开发者')}
{$t('应用管理员')}
{$t('消费者开发者')}
{$t('消费者管理员')}
{$t('驱动名称')}
{$t('请求失败数')}
{$t('转发失败数')}
@@ -53,8 +53,8 @@ export const TranslateWord = ()=>{
{$t('上传 OpenAPI 文档 (.json/.yaml)')}
{$t('替换 OpenAPI 文档 (.json/.yaml)')}
{$t('打开 OpenAPI YAML 编辑器')}
{$t('无需审核:允许任何应用调用该服务')}
{$t('人工审核:仅允许通过人工审核的应用调用该服务')}
{$t('无需审核:允许任何消费者调用该服务')}
{$t('人工审核:仅允许通过人工审核的消费者调用该服务')}
{$t('永久')}
{$t('否')}
{$t('是')}
@@ -72,7 +72,7 @@ export const TranslateWord = ()=>{
{$t('版本状态')}
{$t('创建人')}
{$t('审核时间')}
{$t('申请方-应用')}
{$t('申请方-消费者')}
{$t('审核状态')}
{$t('申请人')}
{$t('审核人')}
@@ -132,6 +132,11 @@ export const TranslateWord = ()=>{
{$t('每小时')}
{$t('每天')}
{$t('每周')}
{$t('上线结果')}
{$t('订阅服务数量')}
{$t('鉴权数量')}
{$t('列表')}
{$t('块')}
</>
)
@@ -15,7 +15,7 @@ export const SUBSCRIBE_APPROVAL_TABLE_COLUMN : PageProColumns<ApprovalTableListI
},
},
{
title:('申请方-应用'),
title:('申请方-消费者'),
dataIndex: ['application','name'],
ellipsis:true
},
@@ -76,7 +76,7 @@ export const SUBSCRIBE_APPROVAL_INNER_TODO_TABLE_COLUMN : PageProColumns<Subscri
},
},
{
title:('申请方-应用'),
title:('申请方-消费者'),
dataIndex: ['application','name'],
ellipsis:true
},
@@ -111,7 +111,7 @@ export const SUBSCRIBE_APPROVAL_INNER_DONE_TABLE_COLUMN : PageProColumns<Subscri
},
},
{
title:('申请方-应用'),
title:('申请方-消费者'),
dataIndex: ['application','name'],
ellipsis:true
},
@@ -445,7 +445,7 @@ export const ChangeTypeEnum = {
export const SubscribeApprovalList = [
{
title:('申请方应用'),key:'application'
title:('申请方消费者'),key:'application'
},
{
title:('申请方所属团队'),key:'applyTeam'
+8 -3
View File
@@ -20,8 +20,8 @@ export const STATUS_COLOR = {
// avoid changing route within ths same category
export const routerKeyMap = new Map<string, string[]|string>([
['workspace',['tenantManagement','service','team','guide']],
['my',['tenantManagement','service','team']],
['workspace',['consumer','service','team','guide']],
['my',['consumer','service','team']],
['mainPage',['dashboard','systemrunning']],
['operationCenter',['member','user','role','common']],
['organization',['member','user','role']],
@@ -68,4 +68,9 @@ export const routerKeyMap = new Map<string, string[]|string>([
export const DELETE_TIPS = {
default:('该数据删除后将无法找回,请确认是否删除?')
}
}
export const DATA_SHOW_TYPE_OPTIONS = [
{label:'列表', value:'list'},
{label:'块', value:'block'},
]
@@ -1,8 +1,8 @@
{
"工作空间": "Kc0e5ef9f",
"首页": "K4de11e23",
"应用": "Kfe93ef35",
"服务": "Kb58e0c3f",
"消费者": "K7acfcfad",
"团队": "Kc9e489f5",
"API 市场": "K61c89f5f",
"仪表盘": "K16d71239",
@@ -95,8 +95,8 @@
"只读成员": "Ke41d7451",
"服务管理员": "Kf99e8b66",
"服务开发者": "Kda8db57a",
"应用开发者": "K216a1ac7",
"应用管理员": "K27924db",
"消费者开发者": "Kc8054dba",
"消费者管理员": "Keb1673a6",
"驱动名称": "K8dc5c723",
"请求失败数": "Kda249fe8",
"转发失败数": "Kcf2df651",
@@ -131,8 +131,8 @@
"上传 OpenAPI 文档 (.json/.yaml)": "K6206e4ad",
"替换 OpenAPI 文档 (.json/.yaml)": "Kfba46e6d",
"打开 OpenAPI YAML 编辑器": "Kdac8ce7e",
"无需审核:允许任何应用调用该服务": "Kffd7e274",
"人工审核:仅允许通过人工审核的应用调用该服务": "K8a8b13e4",
"无需审核:允许任何消费者调用该服务": "K1fc2cc28",
"人工审核:仅允许通过人工审核的消费者调用该服务": "K8dabb98e",
"永久": "Kbfe02d7f",
"否": "K1e9c479e",
"是": "Kaddfcb6b",
@@ -150,7 +150,7 @@
"版本状态": "Kcbf39b82",
"创建人": "K339d15b5",
"审核时间": "K7194cea2",
"申请方-应用": "K831aa6c0",
"申请方-消费者": "K7c1fb123",
"审核状态": "K7ad449bc",
"审核人": "K3b3a98ce",
"来源": "K61b62ace",
@@ -207,6 +207,11 @@
"每小时": "Kf00f01ca",
"每天": "Kfcda87fc",
"每周": "K29ec75dc",
"上线结果": "K93c2696e",
"订阅服务数量": "K6e32a344",
"鉴权数量": "Ka701316",
"列表": "K9eaa2eb6",
"块": "Kfaec39e9",
"暂无操作权限,请联系管理员分配。": "K23fda291",
"微信小程序": "K4618cb0a",
"获取文件,需填路径": "Ka854f511",
@@ -369,7 +374,7 @@
"创建 AI 服务和 API": "Kc057704a",
"创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。": "K76bb4a09",
"创建调用 Token": "K71b2c70f",
"为了安全地调用 API,你需要创建一个应用以及Token。": "K9bdd8403",
"为了安全地调用 API,你需要创建一个消费者以及Token。": "Kdea9a418",
"调用": "Kc5738b6c",
"现在你可以通过 Token 来调用这些 API。": "Kd6d7ca1f",
"快速接入 REST API": "K86cf95f",
@@ -379,13 +384,13 @@
"核心功能": "K2cdbb773",
"账号与角色": "K3378c50d",
"邀请你的团队成员加入 APIPark,共同管理和调用 API。": "Kda5bb930",
"团队中包含了人员、应用和服务,不同团队之间的应用和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。": "Kc8239422",
"团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。": "Keee27105",
"服务内包含一组 API,并且可以发布到 API 市场被其他团队使用。": "Kd5be0cd7",
"权限管理": "K62e89ee7",
"订阅服务": "K8f7808e6",
"如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审核后才可发起 API 请求。": "Kf2410413",
"审核订阅申请": "K6c2e44b8",
"提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的应用才可发起 API 请求。": "Ka0a8840a",
"提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的消费者才可发起 API 请求。": "Kaa717866",
"APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。": "K3453272",
"Hello!欢迎使用 APIPark": "Kd518ba3e",
"你能通过 APIPark 快速在企业内部构建 API 开放门户/市场,享受极致的转发性能、API 可观测、服务治理、多租户管理、订阅审核流程等诸多好处。": "K7e04ea16",
@@ -396,10 +401,10 @@
"了解 APIPark 如何更好地管理 API 和 AI": "K1afaf20e",
"了解更多功能": "K48f7e21f",
"隐藏该教程": "K698296e2",
"登录": "Kd2c1a316",
"请输入账号": "Kf076f63c",
"请输入密码": "K25c895d5",
"密码": "K551b0348",
"登录": "Kd2c1a316",
"访客模式": "K192b3e38",
"您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。": "K91aa4801",
"Version (0)-(1)": "K480045ce",
@@ -480,6 +485,8 @@
"只允许上传PNG、JPG或SVG格式的图片": "Ka9c08390",
"服务名称": "K413b9869",
"服务类型": "K9919285b",
"AI 服务": "Kd2c34e2c",
"REST 服务": "K62840d62",
"默认 AI 供应商": "Kcef64f4d",
"创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API": "K300c89d4",
"未配置任何 AI 模型供应商,": "Kcab588a9",
@@ -518,7 +525,7 @@
"添加成员": "Kec46a57f",
"输入姓名查找": "K48724410",
"搜索用户名、邮箱": "Kb9052305",
"设置团队和成员,然后你可以在团队内创建服务和应用、订阅API,成员只能看到所属团队内的服务和应用。": "K5ece3bac",
"设置团队和成员,然后你可以在团队内创建服务和消费者、订阅API,成员只能看到所属团队内的服务和消费者。": "K4c72fb6f",
"添加团队": "K510cdd27",
"输入名称、ID、负责人查找团队": "K9244ae14",
"配置团队": "Kc7b24b4b",
@@ -538,8 +545,8 @@
"导出": "Ka2c794a2",
"退出全屏": "Kaf70c3b",
"(0)调用详情": "Kd22841a4",
"应用调用统计": "K1512e983",
"请选择应用": "Kb4d2007f",
"消费者调用统计": "K61cca533",
"请选择消费者": "Kdfff59d4",
"调用趋势": "K8c7f2d2e",
"(0)-(1)调用趋势": "K657c3452",
"调用量统计": "Kc04efb87",
@@ -562,7 +569,7 @@
"请选择服务": "Kffcfe375",
"调用详情": "Ka65f739c",
"API 请求量 Top10": "K89b7ac79",
"应用调用量 Top10": "Kc0915603",
"消费者调用量 Top10": "K386857bd",
"服务被调用量 Top10": "Kf90b54",
"暂无请求统计数据": "Kfb26388",
"请求统计": "Kc8cbd8f8",
@@ -585,9 +592,9 @@
"万": "Ke6a935d",
"搜索分类或标签": "Kd59290a2",
"暂无API数据": "K6b75bdbc",
"搜索或选择应用": "Kd8a7a689",
"搜索或选择消费者": "Kb684c806",
"申请理由": "K4b15d6f5",
"应用管理": "Kb7e869a4",
"消费者管理": "Ke0fbd1c8",
"鉴权类型": "Kb71b5a13",
"Iss": "K4d1465ee",
"签名算法": "K5dcd7ed8",
@@ -600,9 +607,9 @@
"SK": "K31418470",
"Apikey": "Kbfeb5297",
"隐藏鉴权信息": "Ke64e43a",
"应用名称": "K5168eb63",
"应用 ID": "K546e46f",
"删除应用": "K95764d1d",
"消费者名称": "K67b530f",
"消费者 ID": "K11f34de",
"删除消费者": "Kc01002",
"鉴权详情": "K217cb125",
"添加鉴权": "K2bb63eca",
"编辑鉴权": "Kd74d69b7",
@@ -619,9 +626,11 @@
"请确认是否取消订阅申请?": "K1856c229",
"搜索服务": "K66ea2f0",
"审核中": "K8adf7f8b",
"添加应用": "K667bbbe7",
"添加消费者": "K84c4dc71",
"暂无服务描述": "Ka4b45550",
"创建并管理自己的消费者实体,每个消费者可以订阅多个API服务,确保在调用之前已获得相应权限。你可以为消费者生成 API 密钥等鉴权方式,用于安全地调用 API 服务": "K5c4e2865",
"订阅的服务数量:已通过 (0) 个,申请中 (1) 个": "K3c7b175f",
"输入名称、ID 查找服务": "K40a89bd8",
"退出测试": "Kbe3e9335",
"服务市场": "K370a3eb2",
"服务详情": "Kf7ec36d",
@@ -630,14 +639,14 @@
"Base URL": "Kc29dabf2",
"申请": "K4aa9ed2c",
"服务信息": "K6c060779",
"接入应用": "K8723422e",
"接入消费者": "Kba74f26d",
"供应方": "Kb97544cb",
"分类": "Kb32f0afe",
"版本": "K81634069",
"更新时间": "Keefda53d",
"无标签": "K96a2f1c8",
"API 数量": "K72b0c0b3",
"接入应用数量": "K93d5a66e",
"接入消费者数量": "K70b79760",
"关联标签": "K96059c69",
"更新者": "K8b7c2592",
"添加 Open Api": "K32263abd",
@@ -1,7 +1,6 @@
{
"Kc0e5ef9f": "Workspace",
"K4de11e23": "Home",
"Kfe93ef35": "Application",
"Kb58e0c3f": "Service",
"Kc9e489f5": "Team",
"K61c89f5f": "API Portal",
@@ -91,8 +90,6 @@
"K6206e4ad": "Upload OpenAPI File (.json/.yaml)",
"Kfba46e6d": "Replace OpenAPI File (.json/.yaml)",
"Kdac8ce7e": "Open OpenAPI YAML Editor",
"Kffd7e274": "No Review: All applications are allowed to subscribe to this service",
"K8a8b13e4": "Manual Review: Only reviewed and approved applications can subscribe to this service",
"Kbfe02d7f": "Permanent",
"K1e9c479e": "No",
"Kaddfcb6b": "Yes",
@@ -259,7 +256,6 @@
"Kc057704a": "Create AI Services and APIs",
"K76bb4a09": "Create AI-based services, and you can set the prompt as an API to simplify the process of using AI.",
"K71b2c70f": "Create Call Token",
"K9bdd8403": "To securely call APIs, you need to create an application and a token.",
"Kc5738b6c": "Call",
"Kd6d7ca1f": "You can now call these APIs with the token.",
"K86cf95f": "Quickly Integrate REST API",
@@ -269,13 +265,11 @@
"K2cdbb773": "Core Features",
"K3378c50d": "Account and Role",
"Kda5bb930": "Invite your team members to APIPark to collaboratively manage and call APIs.",
"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 Marketplace for use by other teams.",
"K62e89ee7": "Permission Management",
"K8f7808e6": "Subscribe to Services",
"Kf2410413": "To call an API from a service, you need to subscribe to that service and wait for approval from the providing team before initiating the API request.",
"K6c2e44b8": "Review Subscription",
"Ka0a8840a": "Review subscription requests from other applications. Only approved requests can initiate API calls.",
"K3453272": "APIPark provides detailed API call logs, helping enterprises monitor, analyze, and audit API operations.",
"Kd518ba3e": "Hello! Welcome to APIPark",
"K7e04ea16": "🦄 APIPark is an open-source, all-in-one AI gateway and API developer portal, enabling enterprises and developers to quickly integrate over 100 AI models, combine AI models and prompts into new APIs, and standardize all AI request data formats, ensuring that switching AI models or adjusting prompts does not affect your APP or microservice. Additionally, APIParks developer portal allows you to share APIs within your team, manage applications that call your APIs, and ensure API security, while monitoring your AI API usage with clear charts.",
@@ -405,7 +399,6 @@
"Kec46a57f": "Add Member",
"K48724410": "Enter Name to Search",
"Kb9052305": "Search Username, Email",
"K5ece3bac": "Set up teams 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 Teams",
"Kc7b24b4b": "Configure Team",
@@ -425,8 +418,6 @@
"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",
@@ -449,7 +440,6 @@
"Kffcfe375": "Please Select Service",
"Ka65f739c": "Call Details",
"K89b7ac79": "API Top 10",
"Kc0915603": "Application Top 10",
"Kf90b54": "Service Top 10",
"Kfb26388": "No Request Data",
"Kc8cbd8f8": "Request Statistics",
@@ -472,9 +462,7 @@
"Ke6a935d": "Ten Thousand",
"Kd59290a2": "Search Category or Tag",
"K6b75bdbc": "No API Data",
"Kd8a7a689": "Search or Select Application",
"K4b15d6f5": "Application Reason",
"Kb7e869a4": "Application Settings",
"Kb71b5a13": "Authentication Type",
"K4d1465ee": "ISS",
"K5dcd7ed8": "Signature Algorithm",
@@ -488,9 +476,6 @@
"Kbfeb5297": "API Key",
"K1a78e6f0": "Expiration Time",
"Ke64e43a": "Hide Authentication Info",
"K5168eb63": "Application Name",
"K546e46f": "Application ID",
"K95764d1d": "Delete Application",
"K217cb125": "Authentication Details",
"K2bb63eca": "Add Authentication",
"Kd74d69b7": "Edit Authentication",
@@ -507,7 +492,6 @@
"K1856c229": "Are you sure you want to cancel the subscription request?",
"K66ea2f0": "Search Services",
"K8adf7f8b": "Under Review",
"K667bbbe7": "Add Application",
"Ka4b45550": "No Service Description",
"K3c7b175f": "Number of Subscribed Services: (0) Approved, (1) Pending",
"Kbe3e9335": "Exit Test",
@@ -517,7 +501,6 @@
"K59cdbec3": "Intro",
"K4aa9ed2c": "Subscribe",
"K6c060779": "Service Information",
"K8723422e": "Access Application",
"Kb97544cb": "Provider",
"Kb32f0afe": "Category",
"K81634069": "Version",
@@ -525,7 +508,6 @@
"Keefda53d": "Update Time",
"K96a2f1c8": "No Tags",
"K72b0c0b3": "Number of APIs",
"K93d5a66e": "Number of Access Applications",
"K96059c69": "Associated Tags",
"K32263abd": "Add Open API",
"K7829bb78": "Configure Open API",
@@ -546,8 +528,6 @@
"Ke41d7451": "Read-Only Member",
"Kf99e8b66": "Service Administrator",
"Kda8db57a": "Service Developer",
"K216a1ac7": "Application Developer",
"K27924db": "Application Administrator",
"K8dc5c723": "Driver Name",
"Kda249fe8": "Failure",
"Kcf2df651": "Failure",
@@ -575,7 +555,6 @@
"K593e0c7e": "No Review Required",
"Ke2d747d9": "Review Required",
"Kc29dabf2": "Base URL",
"Kd55c6887": "Review",
"K300c89d4": "When creating an API, this provider is selected by default. Changing the default provider will not affect existing APIs.",
"Kefaf9956": "Create Time",
"Kad1c674c": "Protocol",
@@ -589,7 +568,6 @@
"Kcbf39b82": "Status",
"K339d15b5": "Creator",
"K7194cea2": "Review Time",
"K831aa6c0": "Applicant - Application",
"K7ad449bc": "Status",
"K3b3a98ce": "Reviewer",
"K61b62ace": "Source",
@@ -646,5 +624,35 @@
"K29ec75dc": "Per Week",
"Kac172626": "Please provide a reason when rejecting the request.",
"Ke1b1865": "Private",
"K4786c57c": "Public"
"K4786c57c": "Public",
"K7acfcfad": "Consumer",
"Kc8054dba": "Consumer Developer",
"Keb1673a6": "Consumer Administrator",
"K1fc2cc28": "No Review: All consumers are allowed to subscribe to this service",
"K8dabb98e": "Manual Review: Only reviewed and approved consumers can subscribe to this service",
"K7c1fb123": "Applicant - Application",
"Kdea9a418": "To securely call APIs, you need to create an consumer and a token.",
"Keee27105": "Teams include personnel, consumers, and services. Data between different teams is isolated, and can be used to manage different departments/project teams/teams within the enterprise.",
"Kaa717866": "Review subscription requests from other consumers. Only approved requests can initiate API calls.",
"Kd2c34e2c": "AI Service",
"K62840d62": "REST Service",
"K4c72fb6f": "Set up teams and members. You can then create services and consumers, subscribe to APIs within the team. Members can only see services and applications within their team.",
"K61cca533": "Consumer Call Statistics",
"Kdfff59d4": "Please Select Consumer",
"K386857bd": "Consumer Top 10",
"Kb684c806": "Search or Select Consumer",
"Ke0fbd1c8": "Consumer Settings",
"K67b530f": "Consumer Name",
"K11f34de": "Consumer ID",
"Kc01002": "Delete Consumer",
"K84c4dc71": "Add Consumer",
"K5c4e2865": "Create and manage your own consumer entities, with each consumer able to subscribe to multiple API services, ensuring the necessary permissions are obtained before making any calls. You can generate API keys or other credentials for consumers to securely call API services.",
"K40a89bd8": "Enter Name, ID to Search Member",
"Kba74f26d": "Access Consumer",
"K70b79760": "Number of Access Consumers",
"K93c2696e": "Online Result",
"K6e32a344": "Number of Services",
"Ka701316": "Number of Auths",
"K9eaa2eb6": "List",
"Kfaec39e9": "Block"
}
@@ -0,0 +1 @@
{}
@@ -0,0 +1,32 @@
{
"K7acfcfad": "消费者",
"Kc8054dba": "消费者开发者",
"Keb1673a6": "消费者管理员",
"K1fc2cc28": "无需审核:允许任何消费者调用该服务",
"K8dabb98e": "人工审核:仅允许通过人工审核的消费者调用该服务",
"K7c1fb123": "申请方-消费者",
"K93c2696e": "上线结果",
"K6e32a344": "订阅服务数量",
"Ka701316": "鉴权数量",
"K9eaa2eb6": "列表",
"Kfaec39e9": "块",
"Kdea9a418": "为了安全地调用 API,你需要创建一个消费者以及Token。",
"Keee27105": "团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。",
"Kaa717866": "提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的消费者才可发起 API 请求。",
"Kd2c34e2c": "AI 服务",
"K62840d62": "REST 服务",
"K4c72fb6f": "设置团队和成员,然后你可以在团队内创建服务和消费者、订阅API,成员只能看到所属团队内的服务和消费者。",
"K61cca533": "消费者调用统计",
"Kdfff59d4": "请选择消费者",
"K386857bd": "消费者调用量 Top10",
"Kb684c806": "搜索或选择消费者",
"Ke0fbd1c8": "消费者管理",
"K67b530f": "消费者名称",
"K11f34de": "消费者 ID",
"Kc01002": "删除消费者",
"K84c4dc71": "添加消费者",
"K5c4e2865": "创建并管理自己的消费者实体,每个消费者可以订阅多个API服务,确保在调用之前已获得相应权限。你可以为消费者生成 API 密钥等鉴权方式,用于安全地调用 API 服务",
"K40a89bd8": "输入名称、ID 查找服务",
"Kba74f26d": "接入消费者",
"K70b79760": "接入消费者数量"
}
@@ -0,0 +1,7 @@
{
"K93c2696e": "上线结果",
"K6e32a344": "订阅服务数量",
"Ka701316": "鉴权数量",
"K9eaa2eb6": "列表",
"Kfaec39e9": "块"
}
@@ -0,0 +1,32 @@
{
"K7acfcfad": "消费者",
"Kc8054dba": "消费者开发者",
"Keb1673a6": "消费者管理员",
"K1fc2cc28": "无需审核:允许任何消费者调用该服务",
"K8dabb98e": "人工审核:仅允许通过人工审核的消费者调用该服务",
"K7c1fb123": "申请方-消费者",
"K93c2696e": "上线结果",
"K6e32a344": "订阅服务数量",
"Ka701316": "鉴权数量",
"K9eaa2eb6": "列表",
"Kfaec39e9": "块",
"Kdea9a418": "为了安全地调用 API,你需要创建一个消费者以及Token。",
"Keee27105": "团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。",
"Kaa717866": "提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的消费者才可发起 API 请求。",
"Kd2c34e2c": "AI 服务",
"K62840d62": "REST 服务",
"K4c72fb6f": "设置团队和成员,然后你可以在团队内创建服务和消费者、订阅API,成员只能看到所属团队内的服务和消费者。",
"K61cca533": "消费者调用统计",
"Kdfff59d4": "请选择消费者",
"K386857bd": "消费者调用量 Top10",
"Kb684c806": "搜索或选择消费者",
"Ke0fbd1c8": "消费者管理",
"K67b530f": "消费者名称",
"K11f34de": "消费者 ID",
"Kc01002": "删除消费者",
"K84c4dc71": "添加消费者",
"K5c4e2865": "创建并管理自己的消费者实体,每个消费者可以订阅多个API服务,确保在调用之前已获得相应权限。你可以为消费者生成 API 密钥等鉴权方式,用于安全地调用 API 服务",
"K40a89bd8": "输入名称、ID 查找服务",
"Kba74f26d": "接入消费者",
"K70b79760": "接入消费者数量"
}
@@ -0,0 +1,24 @@
{
"Kfe93ef35": "Application",
"Kffd7e274": "No Review: All applications are allowed to subscribe to this service",
"K8a8b13e4": "Manual Review: Only reviewed and approved applications can subscribe to this service",
"K9bdd8403": "To securely call APIs, you need to create an application and a token.",
"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.",
"Ka0a8840a": "Review subscription requests from other applications. Only approved requests can initiate API calls.",
"K5ece3bac": "Set up teams 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.",
"K1512e983": "Application Call Statistics",
"Kb4d2007f": "Please Select Application",
"Kc0915603": "Application Top 10",
"Kd8a7a689": "Search or Select Application",
"Kb7e869a4": "Application Settings",
"K5168eb63": "Application Name",
"K546e46f": "Application ID",
"K95764d1d": "Delete Application",
"K667bbbe7": "Add Application",
"K8723422e": "Access Application",
"K93d5a66e": "Number of Access Applications",
"K216a1ac7": "Application Developer",
"K27924db": "Application Administrator",
"Kd55c6887": "Review",
"K831aa6c0": "Applicant - Application"
}
@@ -0,0 +1,24 @@
{
"Kfe93ef35": "Application",
"Kffd7e274": "無審査:すべてのアプリケーションがこのサービスにサブスクライブできます",
"K8a8b13e4": "手動審査:承認されたアプリケーションのみがこのサービスにサブスクライブできます",
"K9bdd8403": "API を安全に呼び出すためには、アプリケーションとトークンを作成する必要があります。",
"Kc8239422": "チームにはユーザー、アプリケーション、サービスが含まれ、異なるチームのアプリケーションとサービスのデータは分離されています。企業内の部門/プロジェクトグループ/チームの管理に使用できます。",
"Ka0a8840a": "他のアプリケーションのサブスクリプション申請をレビューし、承認後に API リクエストが発行できます。",
"K5ece3bac": "チームとメンバーを設定してから、チーム内でサービスとアプリケーションを作成し、API をサブスクライブできます。メンバーは所属チーム内のサービスとアプリケーションのみを表示できます。",
"K1512e983": "アプリケーション呼び出し統計",
"Kb4d2007f": "Application を選択",
"Kc0915603": "Application トップ10",
"Kd8a7a689": "アプリケーションを検索または選択",
"Kb7e869a4": "アプリケーション管理",
"K5168eb63": "Application 名",
"K546e46f": "Application ID",
"K95764d1d": "Application を削除",
"K667bbbe7": "Application を追加",
"K8723422e": "アプリケーションを接続",
"K93d5a66e": "接続アプリケーション数",
"K216a1ac7": "アプリケーション開発者",
"K27924db": "アプリケーション管理者",
"Kd55c6887": "レビュー",
"K831aa6c0": "申請元-アプリケーション"
}
@@ -0,0 +1,24 @@
{
"Kfe93ef35": "消费者",
"Kffd7e274": "无审核:允许所有消费者订阅该服务",
"K8a8b13e4": "人工审核:仅允许审核通过的消费者订阅该服务",
"K9bdd8403": "为了安全地调用 API,你需要创建一个消费者以及Token。",
"Kc8239422": "团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。",
"Ka0a8840a": "审核其他消费者的订阅申请,审核通过后的才可发起 API 请求。",
"K5ece3bac": "设置团队和成员,然后你可以在团队内创建服务和消费者、订阅API,成员只能看到所属团队内的服务和消费者。",
"K1512e983": "消费者调用统计",
"Kb4d2007f": "请选择消费者",
"Kc0915603": "消费者 Top10",
"Kd8a7a689": "搜索或选择消费者",
"Kb7e869a4": "消费者管理",
"K5168eb63": "消费者名称",
"K546e46f": "消费者 ID",
"K95764d1d": "删除消费者",
"K667bbbe7": "添加消费者",
"K8723422e": "接入消费者",
"K93d5a66e": "接入消费者数量",
"K216a1ac7": "消费者开发者",
"K27924db": "消费者管理员",
"Kd55c6887": "审核",
"K831aa6c0": "申请方-消费者"
}
@@ -0,0 +1,24 @@
{
"Kfe93ef35": "應用程式",
"Kffd7e274": "無審核:允許所有應用程式訂閱該服務",
"K8a8b13e4": "人工審核:僅允許審核通過的應用程式訂閱該服務",
"K9bdd8403": "為了安全地調用 API,你需要創建一個應用以及Token。",
"Kc8239422": "團隊中包含了人員、應用程式和服務,不同團隊之間的應用程式和服務數據是隔離的,可用於管理企業內部不同的部門/項目組/團隊。",
"Ka0a8840a": "審核其他應用程式的訂閱申請,審核通過後的才可發起 API 請求。",
"K5ece3bac": "設置團隊和成員,然後你可以在團隊內創建服務和應用程式、訂閱API,成員只能看到所屬團隊內的服務和應用程式。",
"K1512e983": "應用程式調用統計",
"Kb4d2007f": "請選擇應用程式",
"Kc0915603": "應用程式 Top10",
"Kd8a7a689": "搜索或選擇應用程式",
"Kb7e869a4": "應用程式管理",
"K5168eb63": "應用程式名稱",
"K546e46f": "應用程式 ID",
"K95764d1d": "刪除應用程式",
"K667bbbe7": "添加應用程式",
"K8723422e": "接入應用程式",
"K93d5a66e": "接入應用程式數量",
"K216a1ac7": "應用程式開發者",
"K27924db": "應用程式管理員",
"Kd55c6887": "審核",
"K831aa6c0": "申請方-應用程式"
}
@@ -1,7 +1,7 @@
{
"Kc0e5ef9f": "工作空间",
"K4de11e23": "首页",
"Kfe93ef35": "应用",
"Kfe93ef35": "消费者",
"Kb58e0c3f": "服务",
"Kc9e489f5": "团队",
"K61c89f5f": "API 门户",
@@ -91,8 +91,8 @@
"K6206e4ad": "上传 OpenAPI 文档 (.json/.yaml)",
"Kfba46e6d": "替换 OpenAPI 文档 (.json/.yaml)",
"Kdac8ce7e": "打开 OpenAPI YAML 编辑器",
"Kffd7e274": "无审核:允许所有应用订阅该服务",
"K8a8b13e4": "人工审核:仅允许审核通过的应用订阅该服务",
"Kffd7e274": "无审核:允许所有消费者订阅该服务",
"K8a8b13e4": "人工审核:仅允许审核通过的消费者订阅该服务",
"Kbfe02d7f": "永久",
"K1e9c479e": "否",
"Kaddfcb6b": "是",
@@ -259,7 +259,7 @@
"Kc057704a": "创建 AI 服务和 API",
"K76bb4a09": "创建 AI 类型的服务,并且你可以将 Prompt 提示词设置为一个 API,简化使用 AI 的流程。",
"K71b2c70f": "创建调用 Token",
"K9bdd8403": "为了安全地调用 API,你需要创建一个应用以及Token。",
"K9bdd8403": "为了安全地调用 API,你需要创建一个消费者以及Token。",
"Kc5738b6c": "调用",
"Kd6d7ca1f": "现在你可以通过 Token 来调用这些 API。",
"K86cf95f": "快速接入 REST API",
@@ -269,16 +269,16 @@
"K2cdbb773": "核心功能",
"K3378c50d": "账号与角色",
"Kda5bb930": "邀请你的团队成员加入 APIPark,共同管理和调用 API。",
"Kc8239422": "团队中包含了人员、应用和服务,不同团队之间的应用和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。",
"Kc8239422": "团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。",
"Kd5be0cd7": "服务内包含一组 API,并且可以发布到 API 市场被其他团队使用。",
"K62e89ee7": "权限管理",
"K8f7808e6": "订阅服务",
"Kf2410413": "如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审核后才可发起 API 请求。",
"K6c2e44b8": "审核订阅",
"Ka0a8840a": "审核其他应用的订阅申请,审核通过后的才可发起 API 请求。",
"Ka0a8840a": "审核其他消费者的订阅申请,审核通过后的才可发起 API 请求。",
"K3453272": "APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。",
"Kd518ba3e": "Hello!欢迎使用 APIPark",
"K7e04ea16": "🦄 APIPark 是开源的一站式 AI 网关和 API 开发者门户,帮助企业和开发者快速接入 100+ AI 模型,将 AI 模型和 Prompt 提示词组合成新的 API,并且统一所有 AI 的请求数据格式,避免切换 AI 模型或调整提示词时影响你的 APP 应用或者微服务。你还可以通过 APIPark 的开发者门户在团队内共享 API,管理调用的应用并保障你的 API 安全,通过清晰的图表来监控你的 AI API 使用情况。",
"K7e04ea16": "🦄 APIPark 是开源的一站式 AI 网关和 API 开发者门户,帮助企业和开发者快速接入 100+ AI 模型,将 AI 模型和 Prompt 提示词组合成新的 API,并且统一所有 AI 的请求数据格式,避免切换 AI 模型或调整提示词时影响你的 APP 消费者或者微服务。你还可以通过 APIPark 的开发者门户在团队内共享 API,管理调用的消费者并保障你的 API 安全,通过清晰的图表来监控你的 AI API 使用情况。",
"Kedd41c18": "✨ 如果你喜欢 APIPark,欢迎在 Github 为我们 Star 或提供产品反馈意见。",
"Kef02fd87": "快速入门",
"K43a3b38d": "我们提供了一些任务来帮你快速了解 APIPark",
@@ -405,7 +405,7 @@
"Kec46a57f": "添加成员",
"K48724410": "输入姓名查找",
"Kb9052305": "搜索用户名、邮箱",
"K5ece3bac": "设置团队和成员,然后你可以在团队内创建服务和应用、订阅API,成员只能看到所属团队内的服务和应用。",
"K5ece3bac": "设置团队和成员,然后你可以在团队内创建服务和消费者、订阅API,成员只能看到所属团队内的服务和消费者。",
"K510cdd27": "添加团队",
"K9244ae14": "输入名称、ID、负责人查找团队",
"Kc7b24b4b": "设置团队",
@@ -425,8 +425,8 @@
"Ka2c794a2": "导出",
"Kaf70c3b": "退出全屏",
"Kd22841a4": "(0)调用详情",
"K1512e983": "应用调用统计",
"Kb4d2007f": "请选择应用",
"K1512e983": "消费者调用统计",
"Kb4d2007f": "请选择消费者",
"K8c7f2d2e": "调用趋势",
"K657c3452": "(0)-(1)调用趋势",
"Kc04efb87": "调用量统计",
@@ -449,7 +449,7 @@
"Kffcfe375": "请选择服务",
"Ka65f739c": "调用详情",
"K89b7ac79": "API Top10",
"Kc0915603": "应用 Top10",
"Kc0915603": "消费者 Top10",
"Kf90b54": "服务 Top10",
"Kfb26388": "无请求数据",
"Kc8cbd8f8": "请求统计",
@@ -472,9 +472,9 @@
"Ke6a935d": "万",
"Kd59290a2": "搜索分类或标签",
"K6b75bdbc": "无API数据",
"Kd8a7a689": "搜索或选择应用",
"Kd8a7a689": "搜索或选择消费者",
"K4b15d6f5": "申请理由",
"Kb7e869a4": "应用管理",
"Kb7e869a4": "消费者管理",
"Kb71b5a13": "鉴权类型",
"K4d1465ee": "ISS",
"K5dcd7ed8": "签名算法",
@@ -488,9 +488,9 @@
"Kbfeb5297": "API Key",
"K1a78e6f0": "过期时间",
"Ke64e43a": "隐藏认证信息",
"K5168eb63": "应用名称",
"K546e46f": "应用 ID",
"K95764d1d": "删除应用",
"K5168eb63": "消费者名称",
"K546e46f": "消费者 ID",
"K95764d1d": "删除消费者",
"K217cb125": "鉴权详情",
"K2bb63eca": "添加鉴权",
"Kd74d69b7": "编辑鉴权",
@@ -507,7 +507,7 @@
"K1856c229": "是否取消订阅申请?",
"K66ea2f0": "搜索服务",
"K8adf7f8b": "审核中",
"K667bbbe7": "添加应用",
"K667bbbe7": "添加消费者",
"Ka4b45550": "无服务描述",
"K3c7b175f": "订阅服务:已通过 (0) ,审核中 (1)",
"Kbe3e9335": "退出测试",
@@ -517,14 +517,14 @@
"K59cdbec3": "服务介绍",
"K4aa9ed2c": "申请订阅",
"K6c060779": "服务信息",
"K8723422e": "接入应用",
"K8723422e": "接入消费者",
"Kb97544cb": "供应方",
"Kb32f0afe": "分类",
"K81634069": "版本",
"Keefda53d": "更新时间",
"K96a2f1c8": "无标签",
"K72b0c0b3": "API 数量",
"K93d5a66e": "接入应用数量",
"K93d5a66e": "接入消费者数量",
"K96059c69": "关联标签",
"K8b7c2592": "更新者",
"K32263abd": "添加 Open Api",
@@ -546,8 +546,8 @@
"Ke41d7451": "只读成员",
"Kf99e8b66": "服务管理员",
"Kda8db57a": "服务开发者",
"K216a1ac7": "应用开发者",
"K27924db": "应用管理员",
"K216a1ac7": "消费者开发者",
"K27924db": "消费者管理员",
"K8dc5c723": "驱动名称",
"Kda249fe8": "失败",
"Kcf2df651": "失败",
@@ -589,7 +589,7 @@
"Kcbf39b82": "版本状态",
"K339d15b5": "创建人",
"K7194cea2": "审核时间",
"K831aa6c0": "申请方-应用",
"K831aa6c0": "申请方-消费者",
"K7ad449bc": "审核状态",
"K3b3a98ce": "审核人",
"K61b62ace": "来源",
@@ -646,5 +646,30 @@
"Kfcda87fc": "每天",
"K29ec75dc": "每周",
"Ke1b1865": "私有",
"K4786c57c": "公共"
"K4786c57c": "公共",
"K7acfcfad": "消费者",
"Kc8054dba": "消费者开发者",
"Keb1673a6": "消费者管理员",
"K1fc2cc28": "无需审核:允许任何消费者调用该服务",
"K8dabb98e": "人工审核:仅允许通过人工审核的消费者调用该服务",
"K7c1fb123": "申请方-消费者",
"Kdea9a418": "为了安全地调用 API,你需要创建一个消费者以及Token。",
"Keee27105": "团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。",
"Kaa717866": "提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的消费者才可发起 API 请求。",
"Kd2c34e2c": "AI 服务",
"K62840d62": "REST 服务",
"K4c72fb6f": "设置团队和成员,然后你可以在团队内创建服务和消费者、订阅API,成员只能看到所属团队内的服务和消费者。",
"K61cca533": "消费者调用统计",
"Kdfff59d4": "请选择消费者",
"K386857bd": "消费者调用量 Top10",
"Kb684c806": "搜索或选择消费者",
"Ke0fbd1c8": "消费者管理",
"K67b530f": "消费者名称",
"K11f34de": "消费者 ID",
"Kc01002": "删除消费者",
"K84c4dc71": "添加消费者",
"K5c4e2865": "创建并管理自己的消费者实体,每个消费者可以订阅多个API服务,确保在调用之前已获得相应权限。你可以为消费者生成 API 密钥等鉴权方式,用于安全地调用 API 服务",
"K40a89bd8": "输入名称、ID 查找服务",
"Kba74f26d": "接入消费者",
"K70b79760": "接入消费者数量"
}
@@ -1,6 +1,6 @@
/**
* @description api )
* @description api )
* @param request
* @returns
*/
@@ -22,7 +22,7 @@ import { NodeData } from "@core/const/system-running/type"
import { TopologyProjectItem, TopologyServiceItem } from "@core/pages/systemRunning/SystemRunning"
/**
* @description api )
* @description api )
* @param request
* @returns
*/
+18
View File
@@ -272,3 +272,21 @@ a{
stroke: rgba(255, 255, 255, 0.1);
mask-image: radial-gradient(100% 100% at top right, white, transparent);
}
.login-block{
background: rgba(255, 255, 255, 0.1) !important;
.login-input{
color:#fff !important;
background: rgba(255, 255, 255, 0.1) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
&:hover, &:focus, &.ant-input-status-error, &.ant-input-status-error:hover, &.ant-input-status-error:focus-within{
background: rgba(255, 255, 255, 0.2) !important;
border: 1px solid rgba(255, 255, 255, 0.2) !important;
}
}
input:-webkit-autofill,
input:-webkit-autofill:focus {
transition: background-color 0s 600000s, color 0s 600000s !important;
}
}
@@ -366,7 +366,7 @@ const PUBLIC_ROUTES:RouteConfig[] = [
key:uuidv4(),
},
{
path:'tenantManagement',
path:'consumer',
component:<Outlet />,
provider:TenantManagementProvider,
key:uuidv4(),
@@ -1,7 +1,7 @@
export const BASE_GROUP_ORDER = JSON.stringify({})
/**
*
*
*/
export const RELATIVE_PICTURE_NODE_FONTSIZE = 14
@@ -49,14 +49,14 @@ export interface GetProjectGroupRelativeRequest {
}
/**
* ()
* ()
*/
export interface GetSpaceProjectGroupRelativeRequest {
spaceId: string
}
/**
*
*
*/
export interface GetSpaceProjectGroupRelativeResponse {
success: boolean
@@ -69,6 +69,11 @@ export const API_PROTOCOL = [
{label:'HTTPS',value:'HTTPS'}
]
export const API_PATH_MATCH_RULES = [
{label:'前缀匹配',value:'prefix'},
{label:'全等匹配',value:'full'}
]
export const ALGORITHM_ITEM = [
{label:'HS256',value:'HS256'},
@@ -350,12 +355,12 @@ export const PROXY_HEADER_CONFIG:ConfigField<ProxyHeaderItem>[] = [
export const SERVICE_VISUALIZATION_OPTIONS = [
{label:('内部服务:可通过网关访问,但不展示在服务广场'),value:'inner'},
{label:('公开服务:可通过网关访问,展示在服务广场,可被其他应用订阅'),value:'public'}];
{label:('公开服务:可通过网关访问,展示在服务广场,可被其他消费者订阅'),value:'public'}];
export const SERVICE_APPROVAL_OPTIONS = [
{label:('无需审核:允许任何应用调用该服务'),value:'auto'},
{label:('人工审核:仅允许通过人工审核的应用调用该服务'),value:'manual'}];
{label:('无需审核:允许任何消费者调用该服务'),value:'auto'},
{label:('人工审核:仅允许通过人工审核的消费者调用该服务'),value:'manual'}];
export const SERVICE_KIND_OPTIONS = [
{label:('REST'),value:'rest'},
{label:('AI'),value:'ai'}];
+1 -1
View File
@@ -10,7 +10,7 @@ async function initializeApp() {
// 初始化行为
// await fetchInitialConfig(); // 示例:获取初始配置
// 异步操作完成后,渲染React应用
// 异步操作完成后,渲染React消费者
ReactDOM.createRoot(document.getElementById('root')!).render(
<StrictMode>
<GlobalProvider>
+10 -13
View File
@@ -139,7 +139,7 @@ const Login:FC = ()=> {
</div>
{/* <div className="w-full border-box text-right pr-[40px]"></div> */}
<div className="w-[410px] mx-auto flex-1 flex flex-col items-center justify-center z-[3]" >
<div className="mx-auto flex-1 flex flex-col items-center justify-center z-[3]" >
<div className="mx-auto">
<span className="flex items-center justify-center">
<img
@@ -149,14 +149,10 @@ const Login:FC = ()=> {
</span>
</div>
<section className="block w-[410px] mx-auto mt-[46px] bg-MAIN_BG p-[30px] box-border rounded-[10px] shadow-[0_5px_20px_0_rgba(0,0,0,5%)]">
<section className="block w-[410px] mx-auto mt-[46px] p-[30px] box-border rounded-[10px] shadow-[0_5px_20px_0_rgba(0,0,0,5%)] login-block">
<div className="h-full">
<div>
<div className="flex justify-center items-center">
<span className="text-[24px] text-[#101010]">{$t('登录')}</span>
</div>
<Form onFinish={login} className="w-[350px] pt-[28px]"
<div className="">
<Form onFinish={login} className="w-[350px]"
ref={formRef}>
<Form.Item
className="p-0 bg-transparent rounded border-none"
@@ -164,7 +160,7 @@ const Login:FC = ()=> {
rules={[{ required: true, message: $t('请输入账号') ,whitespace:true }]}
>
<Input
className="w-[350px] h-[40px]"
className="w-[350px] h-[40px] login-input"
placeholder={$t("账号")}
autoComplete="on"
autoFocus
@@ -172,12 +168,12 @@ const Login:FC = ()=> {
</Form.Item>
<Form.Item
className="p-0 bg-transparent rounded border-none"
className="p-0 bg-transparent rounded border-none "
name="password"
rules={[{ required: true, message: $t('请输入密码') }]}
>
<Input.Password
className="w-[350px] h-[40px]"
className="w-[350px] h-[40px] login-input"
placeholder={$t("密码")}
autoComplete="off"
/>
@@ -209,8 +205,9 @@ const Login:FC = ()=> {
</section>
<section className="flex flex-col items-center mt-[46px] text-SECOND_TEXT">
<p className="leading-[28px]">{$t('Version (0)-(1)',[state.version,state.updateDate])}</p>
<p className="leading-[28px]">{$t(state.powered)}</p>
<p className="leading-[28px]">
{$t('Version (0)-(1)',[state.version,state.updateDate])}, {$t(state.powered)}
</p>
<LanguageSetting mode="light"/>
</section>
</div>
@@ -1,5 +1,5 @@
import {App, Button, Form, Input, InputNumber, Row, Spin, Tag} from "antd";
import { MutableRefObject, useEffect, useRef, useState} from "react";
import {App, Button, Form, Input, InputNumber, Row, Select, Space, Spin, Tag} from "antd";
import { MutableRefObject, useEffect, useMemo, useRef, useState} from "react";
import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
import {useFetch} from "@common/hooks/http.ts";
import { $t } from "@common/locales/index.ts";
@@ -17,6 +17,9 @@ import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter
import AiServiceRouterModelConfig, { AiServiceRouterModelConfigHandle } from "./AiServiceInsideRouterModelConfig";
import { AiProviderDefaultConfig, AiProviderLlmsItems } from "@core/pages/aiSetting/AiSettingList";
import { EditableFormInstance } from "@ant-design/pro-components";
import { validateUrlSlash } from "@common/utils/validate";
import { API_PATH_MATCH_RULES } from "@core/const/system/const";
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
type AiServiceRouterField = {
name:string
@@ -59,11 +62,16 @@ const AiServiceInsideRouterCreate = () => {
const [defaultLlm, setDefaultLlm] = useState<AiProviderDefaultConfig & {config:string}>()
const [llmList, setLlmList] = useState<AiProviderLlmsItems[]>([])
const [variablesTableRef, setVariablesTableRef] = useState<MutableRefObject<EditableFormInstance<T> | undefined>>()
const {state} = useGlobalContext()
const onFinish = ()=>{
return variablesTableRef?.current?.validateFields().then(()=>{
return form.validateFields().then((formValue)=>{
const {name, path, description, variables, prompt, timeout, retry} = formValue
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}}
const {name, path, description, variables, prompt, timeout, retry,pathMatch} = formValue
const body = {
name,
path: `${prefixForce ? apiPrefix + '/' : ''}${path.trim()}${pathMatch === 'prefix' ? '/*' : ''}`,
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){
@@ -92,7 +100,20 @@ const AiServiceInsideRouterCreate = () => {
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
const {path, aiPrompt,aiModel} = data.api
form.setFieldsValue({...data.api,...aiPrompt, path:prefixForce && path?.startsWith(apiPrefix + '/')? path.slice((apiPrefix?.length || 0) + 1) : path })
let newPath = path
let pathMatch = 'full'
if(prefixForce && path?.startsWith(apiPrefix + '/')){
newPath = path.slice((apiPrefix?.length || 0) + 1)
}
if(newPath.endsWith('/*')){
newPath = newPath.slice(0,-2)
pathMatch = 'prefix'
}
form.setFieldsValue({
...data.api,
...aiPrompt,
path:newPath,
pathMatch})
setVariablesTable(aiPrompt.variables as VariableItems[])
setDefaultLlm(prev => ({...prev, provider: aiModel?.provider, id:aiModel?.id, config:aiModel.config}) as (AiProviderDefaultConfig & { config: string; }))
getDefaultModelConfig(aiModel?.provider)
@@ -141,7 +162,8 @@ const AiServiceInsideRouterCreate = () => {
variables:[{key:'Query',value:'',require:true}],
prompt:'{{Query}}',
retry:0,
timeout:300000
timeout:300000,
pathMatch:'prefix'
})
}
return (form.setFieldsValue({}))
@@ -185,6 +207,9 @@ const AiServiceInsideRouterCreate = () => {
setDrawerType(undefined);
};
const apiPathMatchRulesOptions = useMemo(()=>API_PATH_MATCH_RULES.map(
x=>({label:$t(x.label), value:x.value})),[state.language])
return (
<InsidePage pageTitle={ $t('AI 路由设置')|| '-'}
@@ -230,15 +255,38 @@ const AiServiceInsideRouterCreate = () => {
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
<Form.Item<AiServiceRouterField>
className="flex-1"
label={$t("请求路径")}
name="path"
rules={[{ required: true,whitespace:true }]}
>
<Input prefix={prefixForce ? `${apiPrefix}/` :"/"} className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
<Form.Item label={$t("请求路径")}>
<Space.Compact block>
<Form.Item
name="pathMatch"
rules={[{ required: true,whitespace:true },
{
validator: validateUrlSlash,
}]}
noStyle
>
<Select placeholder={$t(PLACEHOLDER.select)} options={apiPathMatchRulesOptions} className="w-[30%] min-w-[100px]"/>
</Form.Item>
<Form.Item<AiServiceRouterField>
name="path"
rules={[{ required: true,whitespace:true },
{
validator: validateUrlSlash,
}]}
noStyle
>
<Input prefix={(prefixForce ? `${apiPrefix}/` :"/")} className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.input)} onChange={(e)=>{
if((e.target.value as string).endsWith('/*')){
form.setFieldValue('path',e.target.value.slice(0,-2))
form.setFieldValue('pathMatch','prefix')
}
}}/>
</Form.Item>
</Space.Compact>
</Form.Item>
</Row>
<Form.Item<AiServiceRouterField>
@@ -4,12 +4,11 @@ import { BasicResponse, STATUS_CODE, RESPONSE_TIPS } from "@common/const/const";
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, Divider } from "antd";
import { App, Spin, Card, Tag, 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";
import { DefaultOptionType } from "antd/es/select";
import { checkAccess } from "@common/utils/permission";
export type AiSettingListItem = {
@@ -90,7 +89,7 @@ const AiSettingList = ()=>{
}
modal.confirm({
title:$t('模型配置'),
content:<AiSettingModalContent ref={modalRef} entity={data.provider} readOnly={!checkAccess('system.devops.ai_provider.edit', accessData)}/>,
content:<AiSettingModalContent ref={modalRef} entity={{...data.provider,defaultLlm:entity.defaultLlm}} readOnly={!checkAccess('system.devops.ai_provider.edit', accessData)}/>,
onOk:()=>{
return modalRef.current?.save().then((res)=>{if(res === true)
setAiConfigFlushed(true)
@@ -125,46 +124,13 @@ const AiSettingList = ()=>{
}, []);
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 }} />
<div className="flex w-full items-center justify-between gap-[4px]">
<div className="flex flex-1 overflow-hidden items-center gap-[4px]">
<span className=" flex items-center h-[22px] ai-setting-svg-container" dangerouslySetInnerHTML={{ __html: provider.logo }} ></span>
<span className="font-normal truncate">{provider.name}</span>
</div>
<Tag bordered={false} color={provider.configured ? 'green' : undefined} className="h-[22px] px-[4px] text-center">
{provider.configured ? $t('已配置') : $t('未配置')}
</Tag>
@@ -175,18 +141,7 @@ const AiSettingList = ()=>{
<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>
<span className="flex-1 overflow-hidden truncate">{provider.defaultLlm}</span>
</>
}
@@ -245,8 +200,8 @@ const AiSettingList = ()=>{
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) || [] }/>
</>
<ModelCardArea modelList={aiSettingList.filter((item)=>!item.configured) || [] }/>
</>
}
</div>:<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
</Spin>
@@ -1,15 +1,14 @@
import WithPermission from "@common/components/aoplatform/WithPermission";
import { BasicResponse, STATUS_CODE, RESPONSE_TIPS } from "@common/const/const";
import { BasicResponse, STATUS_CODE, RESPONSE_TIPS, PLACEHOLDER } from "@common/const/const";
import { useFetch } from "@common/hooks/http";
import { $t } from "@common/locales";
import { App } from "antd";
import { App, Form, Select, Tag } from "antd";
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import { AiProviderConfig } from "./AiSettingList";
import { AiProviderConfig, AiProviderLlmsItems } from "./AiSettingList";
import { Codebox } from "@common/components/postcat/api/Codebox";
export type AiSettingModalContentProps = {
entity:AiProviderConfig
entity:AiProviderConfig & {defaultLlm:string}
readOnly:boolean
}
@@ -17,31 +16,65 @@ export type AiSettingModalContentHandle = {
save:()=>Promise<boolean|string>
}
type AiSettingModalContentField = {
config:string
defaultLlm:string
}
const AiSettingModalContent = forwardRef<AiSettingModalContentHandle,AiSettingModalContentProps>((props,ref)=>{
const [form] = Form.useForm();
const { message } = App.useApp()
const {entity,readOnly} = props
const {fetchData} = useFetch()
const [code, setCode] = useState<string>()
const [llmList, setLlmList] = useState<AiProviderLlmsItems[]>()
const [loading, setLoading] = useState<boolean>(false)
const getLlmList = ()=>{
setLoading(true)
fetchData<BasicResponse<{llms:AiProviderLlmsItems[]}>>(`ai/provider/llms`,{method:'GET',eoParams:{provider:entity.id}}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setLlmList(data.llms)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).finally(()=>{
setLoading(false)
})
}
useEffect(() => {
getLlmList()
try{
entity!.config && setCode(JSON.stringify(JSON.parse(entity!.config),null,2))
form.setFieldsValue({
defaultLlm:entity.defaultLlm,
config:entity!.config ? JSON.stringify(JSON.parse(entity!.config),null,2) : ''
})
}catch(e){
setCode('')
form.setFieldsValue({
defaultLlm:entity.defaultLlm,
config: ''
})
}
}, []);
const save: ()=>Promise<boolean | string> = ()=>{
return fetchData<BasicResponse<null>>('ai/provider/config',{method:'PUT',eoParams:{provider:entity?.id}, eoBody:({config:code})}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
return Promise.resolve(true)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> Promise.reject(errorInfo))
return new Promise((resolve, reject)=>{
form.validateFields().then((value)=>{
fetchData<BasicResponse<null>>('ai/provider/config',{method:'PUT',eoParams:{provider:entity?.id}, eoBody:value, eoTransformKeys:['defaultLlm']}).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, ()=>({
@@ -50,8 +83,44 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle,AiSettingMo
)
return (
<Codebox editorTheme="vs-dark" readOnly={readOnly}
value={code} onChange={setCode} width="100%" height="300px" language='json' enableToolbar={false} />
<Form
layout='vertical'
labelAlign='left'
scrollToFirstError
form={form}
className="mx-auto flex flex-col h-full"
name="aiServiceInsideRouterModalConfig"
autoComplete="off"
>
<Form.Item<AiSettingModalContentField>
label={$t("模型")}
name="defaultLlm"
rules={[{ required: true }]}
>
<Select className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.select)}
loading={loading}
options={llmList?.map(x=>({
value:x.id,
label:<div className="flex items-center gap-[10px]">
<span>{x.id}</span>
{x?.scopes?.map(s=><Tag >{s?.toLocaleUpperCase()}</Tag>)}
</div>}))}
onChange={(e)=>{
form.setFieldValue('config',llmList?.find(x=>x.id===e)?.config)
}}>
</Select>
</Form.Item>
<Form.Item<AiSettingModalContentField>
label={$t("参数")}
name="config"
>
<Codebox editorTheme="vs-dark" readOnly={readOnly}
width="100%" height="300px" language='json' enableToolbar={false} />
</Form.Item>
</Form>
)
})
@@ -30,7 +30,7 @@ export default function Guide(){
},
{
title: $t("创建调用 Token"),
description: $t('为了安全地调用 API,你需要创建一个应用以及Token。'),
description: $t('为了安全地调用 API,你需要创建一个消费者以及Token。'),
link: 'https://docs.apipark.com/docs/quick/suberscriber/application'
},
{
@@ -50,7 +50,7 @@ export default function Guide(){
},
{
title: $t("创建调用 Token"),
description: $t('为了安全地调用 API,你需要创建一个应用以及Token。'),
description: $t('为了安全地调用 API,你需要创建一个消费者以及Token。'),
link: 'https://docs.apipark.com/docs/quick/suberscriber/subscribe'
},
{
@@ -82,7 +82,7 @@ export default function Guide(){
},
{
title: $t("团队"),
description: $t('团队中包含了人员、应用和服务,不同团队之间的应用和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。'),
description: $t('团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。'),
link: 'https://docs.apipark.com/docs/quick/provider/service'
},
{
@@ -102,7 +102,7 @@ export default function Guide(){
},
{
title: $t("审核订阅申请"),
description: $t('提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的应用才可发起 API 请求。'),
description: $t('提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的消费者才可发起 API 请求。'),
link: 'https://docs.apipark.com/docs/quick/suberscriber/subscribe'
}
]
@@ -3,7 +3,7 @@ import {forwardRef, useEffect, useImperativeHandle, useMemo, useState} from "re
import {App, Button, Form, Input, Radio, Row, Select, TreeSelect, Upload} from "antd";
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 {BasicResponse, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
import {useFetch} from "@common/hooks/http.ts";
import {DefaultOptionType} from "antd/es/cascader";
import { EntityItem, MemberItem, SimpleTeamItem} from "@common/const/type.ts";
@@ -13,7 +13,7 @@ import { validateUrlSlash } from "@common/utils/validate.ts";
import { normFile } from "@common/utils/uploadPic.ts";
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx";
import { useSystemContext } from "../../contexts/SystemContext.tsx";
import { SERVICE_APPROVAL_OPTIONS, SERVICE_KIND_OPTIONS, SERVICE_VISUALIZATION_OPTIONS } from "@core/const/system/const.tsx";
import { SERVICE_APPROVAL_OPTIONS, SERVICE_KIND_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";
@@ -257,7 +257,8 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
setOnEdit(false);
const id = uuidv4()
form.setFieldValue('id',id);
form.setFieldValue('serviceKind',serviceTypeOptions[0].value)
form.setFieldValue('serviceKind','ai')
setShowAI(true)
form.setFieldValue('prefix',`${id.split('-')[0]}/`)
form.setFieldValue('team',teamId);
form.setFieldValue('serviceType','public');
@@ -285,7 +286,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
})
}
const serviceTypeOptions = useMemo(()=>SERVICE_KIND_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])
@@ -324,8 +325,18 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
name="serviceKind"
rules={[{required: true}]}
>
<Select className="w-INPUT_NORMAL" disabled={onEdit} placeholder={$t(PLACEHOLDER.input)} options={serviceTypeOptions} onChange={(value)=>setShowAI(value === 'ai')}>
</Select>
<Radio.Group disabled={onEdit} onChange={(e)=>{setShowAI(e.target.value === 'ai')}} >
<Radio.Button className="w-[180px] h-[100px] mr-btnbase" value="ai">
<div className="w-full h-full flex flex-col items-center justify-center text-black">
<Icon icon="icon-park-outline:robot-one" height={50} width={50} />
<span>{$t('AI 服务')}</span></div>
</Radio.Button>
<Radio.Button className="w-[180px] h-[100px]" value="rest">
<div className="w-full h-full flex flex-col items-center justify-center text-black">
<Icon icon="bx:server" height={50} width={50} />
<span>{$t('REST 服务')}</span></div>
</Radio.Button>
</Radio.Group>
</Form.Item>
}
{showAI && <Form.Item<AiServiceConfigFieldType>
@@ -14,7 +14,6 @@ import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter
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 = ()=>{
@@ -37,7 +36,7 @@ const SystemList:FC = ()=>{
getGlobalAccessData()?.then?.(()=>{
getSystemList()
})
return
return Promise.resolve({data:[], success:false})
}
if(!tableHttpReload){
setTableHttpReload(true)
@@ -1,10 +1,10 @@
import {App, Button, Col, Form, Input, Row, Select, Spin, Switch} from "antd";
import {App, Button, Col, Form, Input, Row, Select, Space, 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"
import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
import {useFetch} from "@common/hooks/http.ts";
import { API_PROTOCOL, HTTP_METHOD, MATCH_CONFIG, MatchPositionEnum, MatchTypeEnum } from "../../../const/system/const.tsx";
import { API_PATH_MATCH_RULES, API_PROTOCOL, HTTP_METHOD, MATCH_CONFIG, MatchPositionEnum, MatchTypeEnum } from "../../../const/system/const.tsx";
import { SystemInsideRouterCreateHandle, SystemInsideRouterCreateProps, SystemApiProxyFieldType, SystemInsideApiProxyHandle } from "../../../const/system/type.ts";
import { MatchItem } from "@common/const/type.ts";
import { validateUrlSlash } from "@common/utils/validate.ts";
@@ -31,7 +31,7 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle,Syste
const onFinish = ()=>{
return Promise.all([proxyRef.current?.validate?.(), form.validateFields()]).then(([,formValue])=>{
const body = {...formValue,
path: prefixForce? `${apiPrefix}/${formValue.path.trim()}` : formValue.path.trim(),
path: `${prefixForce ? apiPrefix + '/' : ''}${formValue.path.trim()}${formValue.pathMatch === 'prefix' ? '/*' : ''}`,
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=>{
@@ -77,7 +77,24 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle,Syste
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
const {disable, protocols, path, methods, description, match, proxy} = data.router
form.setFieldsValue({disable, protocols, path:prefixForce && path?.startsWith(apiPrefix + '/')? path.slice((apiPrefix?.length || 0) + 1) : path, methods, description, match,proxy
let newPath = path
let pathMatch = 'full'
if(prefixForce && path?.startsWith(apiPrefix + '/')){
newPath = path.slice((apiPrefix?.length || 0) + 1)
}
if(newPath.endsWith('/*')){
newPath = newPath.slice(0,-2)
pathMatch = 'prefix'
}
form.setFieldsValue({
disable,
protocols,
path:newPath,
pathMatch,
methods,
description,
match,
proxy
})
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
@@ -94,6 +111,7 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle,Syste
form.setFieldValue(['proxy','timeout'],10000)
form.setFieldValue(['proxy','retry'],0)
form.setFieldValue('protocols',['HTTP','HTTPS'])
form.setFieldValue('pathMatch','prefix')
}
return (form.setFieldsValue({}))
}, []);
@@ -114,6 +132,10 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle,Syste
return {...item}
})
}, [state.language])
const apiPathMatchRulesOptions = useMemo(()=>API_PATH_MATCH_RULES.map(
x=>({label:$t(x.label), value:x.value})),[state.language])
return (
<InsidePage pageTitle={ $t('API 路由设置')|| '-'}
@@ -157,18 +179,35 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle,Syste
<Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.select)} mode="multiple" options={API_PROTOCOL}>
</Select>
</Form.Item>
<Form.Item<SystemApiProxyFieldType>
label={$t("请求路径")}
name="path"
rules={[{ required: true,whitespace:true },
{
validator: validateUrlSlash,
}]}
className={styles['form-input-group']}
>
<Input prefix={(prefixForce ? `${apiPrefix}/` :"/")} className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.input)}/>
<Form.Item label={$t("请求路径")}>
<Space.Compact block>
<Form.Item
name="pathMatch"
rules={[{ required: true,whitespace:true },
{
validator: validateUrlSlash,
}]}
noStyle
>
<Select placeholder={$t(PLACEHOLDER.select)} options={apiPathMatchRulesOptions} className="w-[30%] min-w-[100px]"/>
</Form.Item>
<Form.Item<SystemApiProxyFieldType>
name="path"
rules={[{ required: true,whitespace:true },
{
validator: validateUrlSlash,
}]}
noStyle
>
<Input prefix={(prefixForce ? `${apiPrefix}/` :"/")} className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.input)} onChange={(e)=>{
if((e.target.value as string).endsWith('/*')){
form.setFieldValue('path',e.target.value.slice(0,-2))
form.setFieldValue('pathMatch','prefix')
}
}}/>
</Form.Item>
</Space.Compact>
</Form.Item>
<Form.Item<SystemApiProxyFieldType>
@@ -165,7 +165,7 @@ const TeamList:FC = ()=>{
return (
<InsidePage
pageTitle={$t('团队')}
description={$t("设置团队和成员,然后你可以在团队内创建服务和应用、订阅API,成员只能看到所属团队内的服务和应用。")}
description={$t("设置团队和成员,然后你可以在团队内创建服务和消费者、订阅API,成员只能看到所属团队内的服务和消费者。")}
showBorder={false}
contentClassName=" pr-PAGE_INSIDE_X pb-PAGE_INSIDE_B"
>
@@ -99,7 +99,7 @@ export default function MonitorAppPage(props:MonitorAppPageProps){
fetchTableData(data).then((resp) => {
const {code,data,msg} = resp
if(code === STATUS_CODE.SUCCESS){
exportExcel($t('应用调用统计'), [query!.start!, query!.end!], $t('应用调用统计'), 'dashboard_application', APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG, data.statistics)
exportExcel($t('消费者调用统计'), [query!.start!, query!.end!], $t('消费者调用统计'), 'dashboard_application', APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG, data.statistics)
}else{
message.error(msg || $t(RESPONSE_TIPS.dataError))
}
@@ -144,13 +144,13 @@ export default function MonitorAppPage(props:MonitorAppPageProps){
onTimeRangeChange={handleTimeRangeChange}/>
<div className="flex flex-wrap items-center row-gap-[12px] pt-btnybase mr-btnybase">
<div>
<label className="inline-block whitespace-nowrap">{$t('应用')}</label>
<label className="inline-block whitespace-nowrap">{$t('消费者')}</label>
<Select
className="w-[346px]"
mode="multiple"
maxTagCount={1}
// maxTagPlaceholder={(selectedList) => `and ${selectedList.length} more selected`}
placeholder={$t("请选择应用")}
placeholder={$t("请选择消费者")}
value={queryData?.projects}
options={listOfApps}
onChange={(value)=>{setQueryData(prevData=>({...prevData || {}, projects:value}))}}
@@ -212,7 +212,7 @@ const MonitorTotalPage = (props:MonitorTotalPageProps) => {
children:<MonitorTable className="py-[10px]" ref={monitorApiTableRef} type='api' id="dashboard_top10_api" onRowClick={(record)=>{APP_MODE !== 'pro' ? null : getDetailData(record as MonitorApiData,'api')}} request={()=>getTablesData(queryData||{},'api')}/>
},
{
label:$t('应用调用量 Top10'),
label:$t('消费者调用量 Top10'),
key:'subscribers',
children:<MonitorTable className="py-[10px]" ref={monitorSubTableRef} type='subscribers' id="dashboard_top10_subscriber" onRowClick={(record)=>{APP_MODE !== 'pro' ? null : getDetailData(record as MonitorSubscriberData,'subscriber')}} request={()=>getTablesData(queryData||{},'subscriber')} />
},
@@ -181,8 +181,8 @@ export const DASHBOARD_BASE_COLUMNS_CONFIG:(PageProColumns<MonitorData>&{eoTitle
export const APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG:(PageProColumns<MonitorApiData>&{eoTitle:string})[] = [
{
title:('应用名称'),
eoTitle:('应用名称'),
title:('消费者名称'),
eoTitle:('消费者名称'),
dataIndex: 'name',
width:160,
ellipsis:true,
@@ -190,8 +190,8 @@ export const DASHBOARD_BASE_COLUMNS_CONFIG:(PageProColumns<MonitorData>&{eoTitle
disable:true
},
{
title:('应用 ID'),
eoTitle:('应用 ID'),
title:('消费者 ID'),
eoTitle:('消费者 ID'),
dataIndex: 'id',
width: 140,
ellipsis:true,
@@ -29,7 +29,7 @@ export default function DashboardTabPage(){
children:<Outlet />
},
{
label:$t('应用调用统计'),
label:$t('消费者调用统计'),
key:'provider',
children:<Outlet />
},
@@ -74,7 +74,7 @@ const PUBLIC_ROUTES:RouteConfig[] = [
]
},
{
path:'tenantManagement',
path:'consumer',
component:<Outlet />,
provider:TenantManagementProvider,
key:uuidv4(),
@@ -1,13 +1,12 @@
import { MenuProps } from "antd";
import { getItem } from "@common/utils/navigation";
import { ServiceHubTableListItem } from "./type";
import { PageProColumns } from "@common/components/aoplatform/PageList";
import { frontendTimeSorter } from "@common/utils/dataTransfer";
export const SERVICE_HUB_TABLE_COLUMNS: PageProColumns<ServiceHubTableListItem>[] = [
{
title:('服务名称'),
title:('消费者'),
dataIndex: 'name',
ellipsis:true,
width:160,
@@ -17,32 +16,39 @@ export const SERVICE_HUB_TABLE_COLUMNS: PageProColumns<ServiceHubTableListItem>[
},
},
{
title:('服务ID'),
title:('消费者 ID'),
dataIndex: 'id',
width: 140,
ellipsis:true
},
{
title:('服务标签'),
dataIndex: 'tags',
title:('团队'),
dataIndex: ['team','name'],
ellipsis:true,
renderText:(_,entity:ServiceHubTableListItem)=>entity.tags?.map(x=>x.name).join(',') || '-'
},
{
title:('所属系统'),
dataIndex: ['app','name'],
title:('订阅服务数量'),
dataIndex: 'subscribeNum',
ellipsis:true
},
{
title:('所属团队'),
dataIndex: ['team','name'],
title:('鉴权数量'),
dataIndex: 'authNum',
ellipsis:true
},
{
title:('服务分类'),
dataIndex: ['catalogue','name'],
title:('描述'),
dataIndex:'description',
ellipsis:true
}
},
{
title:('创建时间'),
dataIndex: 'createTime',
width:182,
ellipsis:true,
sorter: (a,b)=>frontendTimeSorter(a,b,'createTime')
},
];
@@ -57,11 +57,11 @@ export const ApplyServiceModal = forwardRef<ApplyServiceHandle,ApplyServiceProps
<Col span={18}>{entity.id}</Col>
</Row>
<Form.Item
label={$t("应用")}
label={$t("消费者")}
name="applications"
rules={[{ required: true }]}
>
<Select className="w-INPUT_NORMAL" disabled={reApply} placeholder={$t("搜索或选择应用")} mode="multiple" options={mySystemOptionList?.filter((x)=>x.value !== entity.id)}/>
<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
@@ -172,7 +172,7 @@ const ServiceHubDetail = ()=>{
</section>
<section className="col-span-1 p-btnbase px-btnrbase">
<Descriptions title={$t("服务信息")} column={1} size={'small'}>
<Descriptions.Item label={$t("接入应用")}>{serviceBasicInfo?.appNum ?? '-'}</Descriptions.Item>
<Descriptions.Item label={$t("接入消费者")}>{serviceBasicInfo?.appNum ?? '-'}</Descriptions.Item>
<Descriptions.Item label={$t("供应方")}>{serviceBasicInfo?.team?.name || '-'}</Descriptions.Item>
<Descriptions.Item label={$t("审核")}>{serviceBasicInfo?.approvalType ? $t((approvalTypeTranslate[serviceBasicInfo?.approvalType] || '-' )): '-'}</Descriptions.Item>
<Descriptions.Item label={$t("分类")}>{serviceBasicInfo?.catalogue?.name || '-'}</Descriptions.Item>
@@ -127,46 +127,46 @@ const ServiceHubList:FC = ()=>{
return (
<ServiceHubGroup filterOption={filterOption} dispatch={dispatch}>
<div className="h-full padding-top-40">
<Spin className="h-full" wrapperClassName="h-full" indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={filterOption.listLoading}>
{filterOption.showServicesList && filterOption.showServicesList.length > 0 ? <VirtuosoGrid
style={{ height: '100%'}}
data={filterOption.showServicesList}
totalCount={filterOption.showServicesList.length}
itemContent={(index) => {
const item = filterOption.showServicesList[index];
return (
<div className="pt-[20px]">
<Card title={CardTitle(item)} className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer h-[180px] m-0 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] ', body:"pt-0"}} onClick={()=>showDocumentDetail(item)}>
<span className="line-clamp-3 text-[12px] text-[#666] "
style={{'word-break':'auto-phrase'}}>{item.description || $t('暂无服务描述')}</span>
</Card>
</div>
);
}}
components={{
List: forwardRef(({ style, children, ...props }, ref) => (
<div
ref={ref}
{...props}
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
columnGap: '20px',
padding:'40px',
...style,
paddingBottom:'40px'
<Spin className="h-full" wrapperClassName="h-full" indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={filterOption.listLoading}>
{filterOption.showServicesList && filterOption.showServicesList.length > 0 ? <VirtuosoGrid
style={{ height: '100%'}}
data={filterOption.showServicesList}
totalCount={filterOption.showServicesList.length}
itemContent={(index) => {
const item = filterOption.showServicesList[index];
return (
<div className="pt-[20px]">
<Card title={CardTitle(item)} className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer h-[180px] m-0 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] ', body:"pt-0"}} onClick={()=>showDocumentDetail(item)}>
<span className="line-clamp-3 text-[12px] text-[#666] "
style={{'word-break':'auto-phrase'}}>{item.description || $t('暂无服务描述')}</span>
</Card>
</div>
);
}}
>
{children}
</div>
)),
Item: ({ children, ...props }) => (
<>
{children}</>
)
}}
/>:<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
</Spin>
components={{
List: forwardRef(({ style, children, ...props }, ref) => (
<div
ref={ref}
{...props}
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
columnGap: '20px',
padding:'40px',
...style,
paddingBottom:'40px'
}}
>
{children}
</div>
)),
Item: ({ children, ...props }) => (
<>
{children}</>
)
}}
/>:<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
</Spin>
</div>
</ServiceHubGroup>
)
@@ -186,7 +186,7 @@ const CardTitle = (service:ServiceHubTableListItem)=>{
<Tooltip title={$t('API 数量')}>
<span className="mr-[12px] flex items-center"><ApiOutlined className="mr-[1px] text-[14px] h-[14px] w-[14px]"/><span className="font-normal text-[14px]">{service.apiNum ?? '-'}</span></span>
</Tooltip>
<Tooltip title={$t('接入应用数量')}>
<Tooltip title={$t('接入消费者数量')}>
<span className="mr-[12px] flex items-center"><span className="h-[14px] mr-[4px] flex items-center "><iconpark-icon size="14px" name="auto-generate-api"></iconpark-icon></span><span className="font-normal text-[14px]">{service.subscriberNum ?? '-'}</span></span>
</Tooltip>
</div>
@@ -8,7 +8,7 @@ export default function ManagementAppSetting(){
return (
<div className=" h-full pt-[32px]">
<div className="flex items-center justify-between w-full ml-[10px] text-[18px] leading-[25px] pb-[16px]" ><span className="font-bold">{$t('应用管理')}</span></div>
<div className="flex items-center justify-between w-full ml-[10px] text-[18px] leading-[25px] pb-[16px]" ><span className="font-bold">{$t('消费者管理')}</span></div>
<div className="h-[calc(100%-41px)] flex flex-col ">
<ManagementConfig type='edit' teamId={teamId!} appId={appId!}/>
</div>
@@ -96,7 +96,7 @@ const ManagementConfig = forwardRef<ManagementConfigHandle,ManagementConfigProps
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
navigate(`/tenantManagement/list`)
navigate(`/consumer/list`)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
@@ -131,7 +131,7 @@ const ManagementConfig = forwardRef<ManagementConfigHandle,ManagementConfigProps
>
<div>
<Form.Item<ManagementConfigFieldType>
label={$t("应用名称")}
label={$t("消费者名称")}
name="name"
rules={[{ required: true,whitespace:true }]}
>
@@ -139,7 +139,7 @@ const ManagementConfig = forwardRef<ManagementConfigHandle,ManagementConfigProps
</Form.Item>
<Form.Item<ManagementConfigFieldType>
label={$t("应用 ID")}
label={$t("消费者 ID")}
name="id"
rules={[{ required: true ,whitespace:true }]}
>
@@ -167,10 +167,10 @@ const ManagementConfig = forwardRef<ManagementConfigHandle,ManagementConfigProps
<WithPermission access="team.application.application.delete" showDisabled={false}>
<div className="bg-[rgb(255_120_117_/_5%)] rounded-[10px] mt-[50px] p-btnrbase pb-0">
<p className="text-left"><span className="font-bold">{$t('删除应用')}</span>{$t('删除操作不可恢复,请谨慎操作!')}</p>
<p className="text-left"><span className="font-bold">{$t('删除消费者')}</span>{$t('删除操作不可恢复,请谨慎操作!')}</p>
<div className="text-left">
<WithPermission access="team.application.application.delete">
<Button className="m-auto mt-[16px] mb-[20px]" type="default" danger={true} onClick={deleteApplicationModal} loading={delBtnLoading}>{$t('删除应用')}</Button>
<Button className="m-auto mt-[16px] mb-[20px]" type="default" danger={true} onClick={deleteApplicationModal} loading={delBtnLoading}>{$t('删除消费者')}</Button>
</WithPermission>
</div>
</div>
@@ -32,7 +32,7 @@ export default function ManagementInsidePage(){
const TENANT_MANAGEMENT_APP_MENU: MenuProps['items'] = useMemo(()=>[
getItem($t('订阅的服务'), 'service'),
getItem($t('访问授权'), 'authorization'),
getItem($t('应用管理'), 'setting'),
getItem($t('消费者管理'), 'setting'),
],[state.language])
const menuData = useMemo(()=>{
@@ -45,7 +45,7 @@ export default function ManagementInsidePage(){
const onMenuClick: MenuProps['onClick'] = (node) => {
setActiveMenu(node.key)
navigateTo(`/tenantManagement/${teamId}/inside/${appId}/${node.key}`)
navigateTo(`/consumer/${teamId}/inside/${appId}/${node.key}`)
};
useEffect(()=>{
@@ -60,7 +60,7 @@ export default function ManagementInsidePage(){
}
setBreadcrumb(
[
{title:<Link to={`/tenantManagement/list/${teamId}`}>{$t('应用')}</Link>},
{title:<Link to={`/consumer/list/${teamId}`}>{$t('消费者')}</Link>},
...(_appName ? [{title:_appName}] : [])
]
)
@@ -84,7 +84,7 @@ useEffect(()=>{
<div className="flex flex-1 h-full">
<div className="w-[220px] border-0 border-solid border-r-[1px] border-r-BORDER">
<div className="text-[18px] leading-[25px] pl-[12px] py-[12px]">
<Button type="text" onClick={()=>navigateTo(`/tenantManagement/list/${teamId}`)}><ArrowLeftOutlined className="max-h-[14px]" />{$t('返回')}</Button>
<Button type="text" onClick={()=>navigateTo(`/consumer/list/${teamId}`)}><ArrowLeftOutlined className="max-h-[14px]" />{$t('返回')}</Button>
</div>
<Menu
onClick={onMenuClick}
@@ -1,7 +1,7 @@
import { MenuProps, Menu, App, Avatar, Card, Tooltip, Empty } from "antd";
import { useState, forwardRef, useEffect, useRef } from "react";
import { MenuProps, Menu, App, Avatar, Card, Tooltip, Empty, Button, Radio } from "antd";
import { useState, forwardRef, useEffect, useRef, useMemo, memo } from "react";
import { VirtuosoGrid } from "react-virtuoso";
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
import { BasicResponse, DATA_SHOW_TYPE_OPTIONS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
import { ServiceHubAppListItem } from "../../../const/serviceHub/type";
import { useFetch } from "@common/hooks/http";
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext";
@@ -14,6 +14,9 @@ import { Icon } from "@iconify/react/dist/iconify.js";
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
import { $t } from "@common/locales";
import WithPermission from "@common/components/aoplatform/WithPermission";
import InsidePage from "@common/components/aoplatform/InsidePage";
import PageList from "@common/components/aoplatform/PageList";
import { SERVICE_HUB_TABLE_COLUMNS } from "@market/const/serviceHub/const";
export default function ServiceHubManagement() {
const { message ,modal} = App.useApp()
@@ -27,30 +30,48 @@ export default function ServiceHubManagement() {
const [teamList, setTeamList] = useState<MenuItem[]>([])
const {setAppName} = useTenantManagementContext()
const navigateTo = useNavigate()
const {getTeamAccessData,cleanTeamAccessData,checkPermission,getGlobalAccessData,accessInit} = useGlobalContext()
const {getTeamAccessData,cleanTeamAccessData,checkPermission,getGlobalAccessData,accessInit,state} = useGlobalContext()
type MenuItem = Required<MenuProps>['items'][number];
const [dataShowType, setDataShowType] = useState<'block'|'list'>('list')
const [tableHttpReload, setTableHttpReload] = useState(true);
const [tableListDataSource, setTableListDataSource] = useState<ServiceHubAppListItem[]>([]);
const [tableSearchWord, setTableSearchWord] = useState<string>('')
const getServiceList = ()=>{
if(!accessInit){
getGlobalAccessData()?.then?.(()=>{getServiceList()})
return
return Promise.resolve({data:[], success:false})
}
if(dataShowType === 'list' && !tableHttpReload){
setTableHttpReload(true)
return Promise.resolve({
data: tableListDataSource,
success: true,
});
}
setServiceLoading(true)
return fetchData<BasicResponse<{apps:ServiceHubAppListItem}>>(!checkPermission('system.workspace.application.view_all') ? 'my_apps':'apps',{method:'GET',eoParams:{ team:teamId,keyword:''},eoTransformKeys:['api_num','subscribe_num','subscribe_verify_num']}).then(response=>{
return fetchData<BasicResponse<{apps:ServiceHubAppListItem}>>(!checkPermission('system.workspace.application.view_all') ? 'my_apps':'apps',{method:'GET',eoParams:{ team:teamId,keyword:tableSearchWord},eoTransformKeys:['api_num','subscribe_num','subscribe_verify_num','auth_num']}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setServiceList([...data.apps,{type:'addNewItem'}])
setTableListDataSource(data.apps)
setTableHttpReload(false)
return {data:data.apps, success: true}
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
return {data:[], success:false}
}
}).catch(() => {
return {data:[], success:false}
}).finally(()=>{
setServiceLoading(false)
})
}
const onClick: MenuProps['onClick'] = (e) => {
navigateTo(`/tenantManagement/list/${e.key}`)
navigateTo(`/consumer/list/${e.key}`)
};
@@ -82,7 +103,7 @@ const getServiceList = ()=>{
let content:string|React.ReactNode = ''
switch (type){
case 'add':
title=$t('添加应用')
title=$t('添加消费者')
content=<ManagementConfig ref={addManagementRef} type={type} teamId={teamId!} />
break;
// case 'edit':{
@@ -124,10 +145,14 @@ const getServiceList = ()=>{
})
}
const dataShowTypeOptions = useMemo(()=>DATA_SHOW_TYPE_OPTIONS.map((x)=>({label:$t(x.label),value:x.value})),[
state.language
])
useEffect(()=>{
if(teamId ){
getTeamAccessData(teamId)
getServiceList()
if(dataShowType === 'block'){getServiceList()}
}
return ()=>{
cleanTeamAccessData()
@@ -137,70 +162,94 @@ useEffect(()=>{
useEffect(() => {
setBreadcrumb(
[
{title:$t('应用')}
{title:$t('消费者')}
]
)
getTeamsList()
setAppName('')
}, []);
const BlockArea = ()=>(
<div className="flex flex-1 h-full">
<div className="w-[220px] border-0 border-solid border-r-[1px] border-r-BORDER h-full overflow-hidden">
<div className="text-[18px] leading-[25px] pl-[20px] pt-[20px] pb-[10px] font-bold">{$t('团队')}</div>
<Menu
onClick={onClick}
style={{ width: 220}}
className="overflow-auto h-[calc(100%-55px)]"
mode="inline"
items={teamList}
selectedKeys={teamId?[teamId]:[]}
/>
</div>
<div className="w-[calc(100%-220px)] padding-top-20">
<div className="mt-[20px] ml-[40px] text-[18px] leading-[25px] font-bold">{$t('消费者')}</div>
<VirtuosoGrid
style={{ height: 'calc(100% - 45px)'}}
data={serviceList}
totalCount={serviceList.length}
itemContent={(index) => {
const item = serviceList[index];
return (
<div className="pt-[20px]">{
item.type === 'addNewItem' ?<WithPermission access="team.application.application.add" showDisabled={false}><Card className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer h-[180px] transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.05]" classNames={{body:'h-[180px] flex items-center justify-center cursor-pointer'}} onClick={()=>{openModal('add')}}>
<div className="flex items-center"><Icon icon="ic:baseline-add" width="18" height="18"/><span>{$t('添加消费者')}</span></div>
</Card></WithPermission> : <Card title={CardTitle(item)} className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer h-[180px] 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] ', body:"pt-0"}} onClick={()=>{setAppName(item.name);navigateTo(`/consumer/${teamId}/inside/${item.id}/service`)}}>
<span className="line-clamp-3 break-all">{item.description || $t('暂无服务描述')}</span>
</Card>}</div>
);
}}
components={{
List: forwardRef(({ style, children, ...props }, ref) => (
<div
ref={ref}
{...props}
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
columnGap: '20px',
padding:'40px',
...style,
paddingBottom:'40px'
}}
>
{children}
</div>
)),
Item: ({ children, ...props }) => (
<>
{children}</>
)
}}
/>
</div>
</div>
)
return (<>{
teamList && teamList.length > 0 ?
<div className="flex flex-1 h-full">
<div className="w-[220px] border-0 border-solid border-r-[1px] border-r-BORDER h-full overflow-hidden">
<div className="text-[18px] leading-[25px] pl-[20px] pt-[20px] pb-[10px] font-bold">{$t('团队')}</div>
<Menu
onClick={onClick}
style={{ width: 220}}
className="overflow-auto h-[calc(100%-55px)]"
mode="inline"
items={teamList}
selectedKeys={teamId?[teamId]:[]}
/>
</div>
<div className="w-[calc(100%-220px)] padding-top-20">
<div className="mt-[20px] ml-[40px] text-[18px] leading-[25px] font-bold">{$t('应用')}</div>
<VirtuosoGrid
style={{ height: 'calc(100% - 45px)'}}
data={serviceList}
totalCount={serviceList.length}
itemContent={(index) => {
const item = serviceList[index];
return (
<div className="pt-[20px]">{
item.type === 'addNewItem' ?<WithPermission access="team.application.application.add" showDisabled={false}><Card className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer h-[180px] transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.05]" classNames={{body:'h-[180px] flex items-center justify-center cursor-pointer'}} onClick={()=>{openModal('add')}}>
<div className="flex items-center"><Icon icon="ic:baseline-add" width="18" height="18"/><span>{$t('添加应用')}</span></div>
</Card></WithPermission> : <Card title={CardTitle(item)} className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible cursor-pointer h-[180px] 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] ', body:"pt-0"}} onClick={()=>{setAppName(item.name);navigateTo(`/tenantManagement/${teamId}/inside/${item.id}/service`)}}>
<span className="line-clamp-3 break-all">{item.description || $t('暂无服务描述')}</span>
</Card>}</div>
);
}}
components={{
List: forwardRef(({ style, children, ...props }, ref) => (
<div
ref={ref}
{...props}
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
columnGap: '20px',
padding:'40px',
...style,
paddingBottom:'40px'
}}
>
{children}
</div>
)),
Item: ({ children, ...props }) => (
<>
{children}</>
)
}}
/>
</div>
</div> :
<InsidePage
className="overflow-y-auto pb-PAGE_INSIDE_B"
pageTitle={$t('消费者')}
description={$t('创建并管理自己的消费者实体,每个消费者可以订阅多个API服务,确保在调用之前已获得相应权限。你可以为消费者生成 API 密钥等鉴权方式,用于安全地调用 API 服务')}
showBorder={false}
scrollPage={false}
contentClassName={dataShowType === 'list' ? 'pr-PAGE_INSIDE_X pb-PAGE_INSIDE_B' :''}
customBtn={
<Radio.Group
options={dataShowTypeOptions}
onChange={(e)=>setDataShowType(e.target.value)}
value={dataShowType}
optionType="button"
buttonStyle="solid"
/>}
>{
dataShowType === 'block' ? <BlockArea /> : <TableArea language={state.language} getServiceList={getServiceList} addNewApp={()=>openModal('add')} setTableHttpReload={setTableHttpReload} setTableSearchWord={setTableSearchWord} editApp={(row:ServiceHubAppListItem)=>{setAppName(row.name);navigateTo(`/consumer/${row.team.id}/inside/${row.id}/service`)}}/>
}
</InsidePage> :
<Empty className="mt-[100px]" image={Empty.PRESENTED_IMAGE_SIMPLE} />
}
</>)
@@ -220,4 +269,39 @@ const CardTitle = (service:ServiceHubAppListItem)=>{
</div>
</div>
)
}
}
type TableAreaProps = {
language:string
getServiceList:()=>Promise<{data:ServiceHubAppListItem[], success:boolean}>
addNewApp:()=>Promise<void>
setTableHttpReload:(b:boolean)=>void
setTableSearchWord:(s:string)=>void
editApp:(item:ServiceHubAppListItem)=>void
}
const TableArea = memo(({language, getServiceList, addNewApp, setTableHttpReload, setTableSearchWord, editApp}:TableAreaProps)=>{
const columns = useMemo(()=>{
const res = SERVICE_HUB_TABLE_COLUMNS.map(x=>{
return {...x,title:typeof x.title === 'string' ? $t(x.title as string) : x.title}})
return res
},[language])
return (
<PageList
id="service_hub_list"
columns={[...columns]}
request={()=>getServiceList()}
addNewBtnTitle={$t("添加服务")}
searchPlaceholder={$t("输入名称、ID 查找服务")}
onAddNewBtnClick={addNewApp}
onChange={() => {
setTableHttpReload(false)
}}
onSearchWordChange={(e) => {
setTableSearchWord(e.target.value)
}}
onRowClick={(row:ServiceHubAppListItem)=>editApp(row)}
/>
)})
@@ -5,14 +5,14 @@ import { $t } from "@common/locales/index.ts";
export const OPENAPI_LIST_COLUMNS: PageProColumns<OpenApiTableListItem>[] = [
{
title:$t('应用名称'),
title:$t('消费者名称'),
dataIndex: 'name',
ellipsis:true,
width:160,
fixed:'left'
},
{
title:$t('应用 ID'),
title:$t('消费者 ID'),
dataIndex: 'id',
ellipsis:true,
width: 140,
@@ -69,7 +69,7 @@ export const OpenApiConfig = forwardRef<OpenApiConfigHandle,OpenApiConfigProps>(
autoComplete="off"
>
<Form.Item<OpenApiConfigFieldType>
label={$t("应用名称")}
label={$t("消费者名称")}
name="name"
rules={[{ required: true,whitespace:true }]}
>
@@ -77,7 +77,7 @@ export const OpenApiConfig = forwardRef<OpenApiConfigHandle,OpenApiConfigProps>(
</Form.Item>
<Form.Item<OpenApiConfigFieldType>
label={$t("应用 ID")}
label={$t("消费者 ID")}
name="id"
rules={[{ required: true ,whitespace:true }]}
>
@@ -216,7 +216,7 @@ export default function OpenApiList(){
dataSource={tableListDataSource}
showPagination={false}
primaryKey="id"
addNewBtnTitle={$t("添加应用")}
addNewBtnTitle={$t("添加消费者")}
addNewBtnAccess="system.openapi.self.add"
onChange={() => {
setTableHttpReload(false)