mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-04 10:13:53 +08:00
Merge remote-tracking branch 'origin/feature/1.8-cx' into feature/liujian-1.8
This commit is contained in:
@@ -50,7 +50,8 @@
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"react-json-view": "^1.21.3",
|
||||
"zod": "^3.23.8",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0"
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"echarts-for-react": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/cssinjs": "^1.18.2",
|
||||
|
||||
@@ -36,7 +36,7 @@ function BasicLayout({ project = 'core' }: { project: string }) {
|
||||
const { state, accessData, checkPermission, accessInit, dispatch, resetAccess, getGlobalAccessData, menuList } =
|
||||
useGlobalContext()
|
||||
const [pathname, setPathname] = useState(currentUrl)
|
||||
const mainPage = project === 'core' ? '/service/list' : '/serviceHub/list'
|
||||
const mainPage = project === 'core' ? '/service/list' : '/portal/list'
|
||||
const [menuItems, setMenuItems] = useState<MenuProps['items']>()
|
||||
const pluginSlotHub = usePluginSlotHub()
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ class InsidePageProps {
|
||||
scrollInsidePage?: boolean = false
|
||||
customPadding?: boolean
|
||||
customBtn?: ReactNode
|
||||
customBanner?: ReactNode
|
||||
}
|
||||
|
||||
const InsidePage: FC<InsidePageProps> = ({
|
||||
@@ -46,7 +47,8 @@ const InsidePage: FC<InsidePageProps> = ({
|
||||
scrollPage = true,
|
||||
scrollInsidePage = false,
|
||||
customPadding = false,
|
||||
customBtn
|
||||
customBtn,
|
||||
customBanner
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -61,8 +63,13 @@ const InsidePage: FC<InsidePageProps> = ({
|
||||
<div
|
||||
className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-solid border-b-[1px] border-BORDER' : ''} ${headerClassName}`}
|
||||
>
|
||||
{!pageTitle && !description && !backUrl && !customBtn ? (
|
||||
{!pageTitle && !description && !backUrl && !customBtn && !customBanner ? (
|
||||
<></>
|
||||
) : customBanner ? (
|
||||
<div className={customPadding ? '' : 'mb-[15px]'}>
|
||||
{backUrl && <TopBreadcrumb handleBackCallback={() => goBack()} />}
|
||||
{customBanner}
|
||||
</div>
|
||||
) : (
|
||||
<div className={customPadding ? '' : 'mb-[30px]'}>
|
||||
{backUrl && <TopBreadcrumb handleBackCallback={() => goBack()} />}
|
||||
|
||||
@@ -58,7 +58,7 @@ interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<Acti
|
||||
delayLoading?: boolean
|
||||
noScroll?: boolean
|
||||
/* 前端分页的表格,需要传入该字段以支持后端搜索 */
|
||||
manualReloadTable?: () => void,
|
||||
manualReloadTable?: () => void
|
||||
customEmptyRender?: () => React.ReactNode
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
const [allowTableClick, setAllowTableClick] = useState<boolean>(false)
|
||||
const { accessData, checkPermission, accessInit, state } = useGlobalContext()
|
||||
const [minTableWidth, setMinTableWidth] = useState<number>(0)
|
||||
const [enableVirtual, setEnableVirtual] = useState(false)
|
||||
|
||||
useImperativeHandle(ref, () => actionRef.current!)
|
||||
|
||||
@@ -301,7 +302,7 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
<ProTable<T>
|
||||
actionRef={actionRef}
|
||||
columns={newColumns}
|
||||
virtual
|
||||
virtual={enableVirtual}
|
||||
scroll={noScroll ? undefined : { x: tableWidth, y: tableHeight }}
|
||||
size="middle"
|
||||
rowSelection={rowSelection}
|
||||
@@ -328,6 +329,10 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
}
|
||||
: false
|
||||
}}
|
||||
postData={(data: any) => {
|
||||
setEnableVirtual(!!data?.length)
|
||||
return data
|
||||
}}
|
||||
showSorterTooltip={false}
|
||||
columnsState={{ persistenceType: 'localStorage', persistenceKey: id }}
|
||||
pagination={
|
||||
|
||||
@@ -27,6 +27,7 @@ type TimeRangeSelectorProps = {
|
||||
bindRef?: any
|
||||
hideBtns?: TimeRangeButton[]
|
||||
defaultTimeButton?: TimeRangeButton
|
||||
customClassNames?: string
|
||||
}
|
||||
const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
const {
|
||||
@@ -38,7 +39,8 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
labelSize = 'default',
|
||||
bindRef,
|
||||
hideBtns = [],
|
||||
defaultTimeButton = 'hour'
|
||||
defaultTimeButton = 'hour',
|
||||
customClassNames = 'pt-btnybase'
|
||||
} = props
|
||||
const [timeButton, setTimeButton] = useState(initialTimeButton || '')
|
||||
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null, null])
|
||||
@@ -111,7 +113,7 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
|
||||
<div className={`flex flex-nowrap items-center ${customClassNames} mr-btnybase`}>
|
||||
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}:</label>}
|
||||
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
|
||||
{hideBtns?.length && hideBtns.includes('hour') ? null : (
|
||||
|
||||
@@ -187,6 +187,7 @@ export const TranslateWord = () => {
|
||||
{$t('调用地址')}
|
||||
{$t('消费者 IP')}
|
||||
{$t('鉴权名称')}
|
||||
{$t('日志输出')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
import { Avatar, Button, Card, Tag, Tooltip, App } from 'antd'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { ApiOutlined } from '@ant-design/icons'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { SERVICE_KIND_OPTIONS } from '@core/const/system/const'
|
||||
import { IconButton } from '@common/components/postcat/api/IconButton'
|
||||
import useCopyToClipboard from '@common/hooks/copy'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
|
||||
export type ServiceBasicInfoType = {
|
||||
id?: string
|
||||
logo?: string
|
||||
name: string
|
||||
description: string
|
||||
appNum: number
|
||||
apiNum: number
|
||||
serviceName: string
|
||||
serviceDesc: string
|
||||
invokeCount: number
|
||||
catalogue: {
|
||||
name: string
|
||||
}
|
||||
serviceKind: string
|
||||
service_kind: string
|
||||
enableMcp: boolean
|
||||
enable_mcp: boolean
|
||||
isReleased?: boolean
|
||||
}
|
||||
|
||||
type ServiceInfoCardProps = {
|
||||
actionSlot?: React.ReactNode
|
||||
customClassName?: string
|
||||
serviceId?: string
|
||||
serviceBasicInfo?: ServiceBasicInfoType
|
||||
teamId?: string
|
||||
}
|
||||
const ServiceInfoCard = ({
|
||||
actionSlot,
|
||||
customClassName,
|
||||
serviceId,
|
||||
serviceBasicInfo,
|
||||
teamId
|
||||
}: ServiceInfoCardProps) => {
|
||||
/** 服务指标 */
|
||||
const [serviceMetrics, setServiceMetrics] = useState<{ title: string; icon: React.ReactNode; value: string }[]>([])
|
||||
/** 服务标签 */
|
||||
const [serviceTags, setServiceTags] = useState<
|
||||
{ color: string; textColor: string; title: string; content: React.ReactNode }[]
|
||||
>([])
|
||||
/** 剪切板 */
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
/** 弹窗组件 */
|
||||
const { message } = App.useApp()
|
||||
/** 获取服务信息 */
|
||||
const { fetchData } = useFetch()
|
||||
/** 服务信息 */
|
||||
const [serviceOverview, setServiceOverview] = useState<ServiceBasicInfoType>()
|
||||
|
||||
/**
|
||||
* 复制
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
const handleCopy = async (value: string): Promise<void> => {
|
||||
if (value) {
|
||||
copyToClipboard(value)
|
||||
message.success($t(RESPONSE_TIPS.copySuccess))
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取服务信息 */
|
||||
const getServiceOverview = () => {
|
||||
fetchData<BasicResponse<{ overview: ServiceBasicInfoType }>>('service/overview/basic', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId, team: teamId },
|
||||
eoTransformKeys: [
|
||||
'api_num',
|
||||
'enable_mcp',
|
||||
'service_kind',
|
||||
'subscriber_num',
|
||||
'invoke_num',
|
||||
'avaliable_monitor',
|
||||
'is_released'
|
||||
]
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const serviceOverview = {
|
||||
...data.overview,
|
||||
appNum: data.overview.subscriberNum,
|
||||
invokeCount: data.overview.invokeNum,
|
||||
serviceName: data.overview.name,
|
||||
serviceDesc: data.overview.description
|
||||
}
|
||||
setServiceOverview(serviceOverview)
|
||||
setServiceMetricsList(serviceOverview)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开服务详情页面
|
||||
*/
|
||||
const openInPortal = () => {
|
||||
window.open(`/portal/detail/${serviceOverview?.id}`, '_blank')
|
||||
}
|
||||
|
||||
// 格式化调用次数,添加K和M单位
|
||||
const formatInvokeCount = (count: number | null | undefined): string => {
|
||||
if (count === null || count === undefined) return '-'
|
||||
if (count >= 1000000) {
|
||||
const value = Math.floor(count / 100000) / 10
|
||||
return `${value}M`
|
||||
}
|
||||
if (count >= 1000) {
|
||||
const value = Math.floor(count / 100) / 10
|
||||
return `${value}K`
|
||||
}
|
||||
return count.toString()
|
||||
}
|
||||
|
||||
const setServiceMetricsList = (serviceOverview: ServiceBasicInfoType) => {
|
||||
// 设置服务指标数据
|
||||
setServiceMetrics([
|
||||
{
|
||||
title: 'API 数量',
|
||||
icon: <ApiOutlined className="mr-[1px] text-[14px] h-[14px] w-[14px]" />,
|
||||
value: serviceOverview.apiNum?.toString() || '0'
|
||||
},
|
||||
{
|
||||
title: '接入消费者数量',
|
||||
icon: <Icon icon="tabler:api-app" width="14" height="14" />,
|
||||
value: serviceOverview.appNum?.toString() || '0'
|
||||
},
|
||||
{
|
||||
title: '30天内调用次数',
|
||||
icon: <Icon icon="iconoir:graph-up" width="14" height="14" />,
|
||||
value: formatInvokeCount(serviceOverview.invokeCount ?? 0)
|
||||
}
|
||||
])
|
||||
const serviceKind = serviceOverview?.serviceKind || serviceOverview?.service_kind
|
||||
// 设置服务标签数据
|
||||
const tags = [
|
||||
{
|
||||
color: '#7371fc1b',
|
||||
textColor: 'text-theme',
|
||||
title: serviceOverview?.catalogue?.name || '-',
|
||||
content: serviceOverview?.catalogue?.name || '-'
|
||||
},
|
||||
{
|
||||
color: `#${serviceKind === 'ai' ? 'EADEFF' : 'DEFFE7'}`,
|
||||
textColor: 'text-[#000]',
|
||||
title: serviceKind || '-',
|
||||
content: SERVICE_KIND_OPTIONS.find((x) => x.value === serviceKind)?.label || '-'
|
||||
}
|
||||
]
|
||||
|
||||
// 如果启用了MCP,添加MCP标签
|
||||
if (serviceOverview?.enableMcp) {
|
||||
tags.push({
|
||||
color: '#FFF0C1',
|
||||
textColor: 'text-[#000]',
|
||||
title: 'MCP',
|
||||
content: 'MCP'
|
||||
})
|
||||
}
|
||||
|
||||
setServiceTags(tags)
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!serviceId && serviceBasicInfo) {
|
||||
setServiceMetricsList(serviceBasicInfo)
|
||||
setServiceOverview(serviceBasicInfo)
|
||||
return
|
||||
}
|
||||
getServiceOverview()
|
||||
}, [serviceId, serviceBasicInfo])
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
style={{
|
||||
borderRadius: '10px',
|
||||
background: 'linear-gradient(35deg, rgb(246, 246, 260) 0%, rgb(255, 255, 255) 40%)'
|
||||
}}
|
||||
className={`w-full ${customClassName}`}
|
||||
classNames={{
|
||||
body: `p-[15px] ${actionSlot ? 'h-[180px]' : 'max-h-[130px]'}`
|
||||
}}
|
||||
>
|
||||
{serviceOverview && (
|
||||
<>
|
||||
<div className="service-info">
|
||||
<div className="flex items-center">
|
||||
<div>
|
||||
<Avatar
|
||||
shape="square"
|
||||
size={50}
|
||||
className={`rounded-[12px] border-none rounded-[12px] ${serviceOverview.logo ? 'bg-[linear-gradient(135deg,white,#f0f0f0)]' : 'bg-theme'}`}
|
||||
src={
|
||||
serviceOverview.logo ? (
|
||||
<img
|
||||
src={serviceOverview.logo}
|
||||
alt="Logo"
|
||||
style={{ maxWidth: '200px', width: '45px', height: '45px', objectFit: 'unset' }}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
icon={serviceOverview.logo ? '' : <Icon icon="tabler:api-app" />}
|
||||
>
|
||||
{' '}
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="pl-[20px] w-[calc(100%-50px)] overflow-hidden">
|
||||
<p
|
||||
className={`text-[14px] h-[20px] leading-[20px] truncate font-bold w-full flex items-center gap-[4px]`}
|
||||
>
|
||||
{serviceOverview.serviceName}
|
||||
</p>
|
||||
<div className="mt-[5px] h-[20px] flex items-center font-normal">
|
||||
{serviceTags.map((tag, index) => (
|
||||
<Tag
|
||||
key={index}
|
||||
color={tag.color}
|
||||
className={`${tag.textColor} font-normal border-0 mr-[12px] max-w-[150px] truncate`}
|
||||
bordered={false}
|
||||
title={tag.title}
|
||||
>
|
||||
{tag.content}
|
||||
</Tag>
|
||||
))}
|
||||
{serviceMetrics.map((item, index) => (
|
||||
<Tooltip key={index} title={$t(item.title)}>
|
||||
<span className="mr-[12px] flex items-center">
|
||||
<span className="h-[14px] mr-[4px] flex items-center">{item.icon}</span>
|
||||
<span className="font-normal text-[14px]">{item.value}</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{serviceOverview.id && (
|
||||
<>
|
||||
<div className="absolute top-[14px] right-[20px]">
|
||||
<span className="bg-white relative py-[2px] pl-[10px] pr-[30px] inline-block border-solid border-[1px] border-BORDER rounded-lg">
|
||||
{$t('服务 ID')}:{serviceOverview.id || '-'}
|
||||
<IconButton
|
||||
name="copy"
|
||||
onClick={() => handleCopy(serviceOverview.id || '')}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
right: '5px',
|
||||
color: '#999',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
</span>
|
||||
<Tooltip title={serviceOverview.isReleased ? '' : $t('服务尚未发布')}>
|
||||
<Button
|
||||
disabled={!serviceOverview.isReleased}
|
||||
className="ml-[10px] !max-h-[28px] rounded-[13px]"
|
||||
type="primary"
|
||||
onClick={() => openInPortal()}
|
||||
>
|
||||
{$t('跳转至详情页')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<span className="line-clamp-2 mt-[15px] text-[12px] text-[#666]" title={serviceOverview.serviceDesc}>
|
||||
{serviceOverview.serviceDesc || $t('暂无服务描述')}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="absolute bottom-[15px]">{actionSlot}</div>
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceInfoCard
|
||||
@@ -0,0 +1,431 @@
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['exports', 'echarts'], factory);
|
||||
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
|
||||
// CommonJS
|
||||
factory(exports, require('echarts'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory({}, root.echarts);
|
||||
}
|
||||
}(this, function (exports, echarts) {
|
||||
var log = function (msg) {
|
||||
if (typeof console !== 'undefined') {
|
||||
console && console.error && console.error(msg);
|
||||
}
|
||||
};
|
||||
if (!echarts) {
|
||||
log('ECharts is not Loaded');
|
||||
return;
|
||||
}
|
||||
echarts.registerTheme('apipark chart palette', {
|
||||
"color": [
|
||||
"#4429e6",
|
||||
"#fd6280",
|
||||
"#28dbe2",
|
||||
"#ffc404",
|
||||
"#b92325",
|
||||
"#1b9f17",
|
||||
"#fe8705",
|
||||
"#97b552",
|
||||
"#95706d",
|
||||
"#dc69aa",
|
||||
"#07a2a4",
|
||||
"#9a7fd1",
|
||||
"#588dd5",
|
||||
"#f5994e",
|
||||
"#333333"
|
||||
],
|
||||
"backgroundColor": "rgba(0,0,0,0)",
|
||||
"textStyle": {},
|
||||
"title": {
|
||||
"textStyle": {
|
||||
"color": "#333333"
|
||||
},
|
||||
"subtextStyle": {
|
||||
"color": "#999999"
|
||||
}
|
||||
},
|
||||
"line": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": "2"
|
||||
},
|
||||
"symbolSize": "5",
|
||||
"symbol": "circle",
|
||||
"smooth": true
|
||||
},
|
||||
"radar": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": "2"
|
||||
},
|
||||
"symbolSize": "5",
|
||||
"symbol": "circle",
|
||||
"smooth": true
|
||||
},
|
||||
"bar": {
|
||||
"itemStyle": {
|
||||
"barBorderWidth": "2",
|
||||
"barBorderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"pie": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"scatter": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"boxplot": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"parallel": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"sankey": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"funnel": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"gauge": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"candlestick": {
|
||||
"itemStyle": {
|
||||
"color": "#d87a80",
|
||||
"color0": "#2ec7c9",
|
||||
"borderColor": "#d87a80",
|
||||
"borderColor0": "#2ec7c9",
|
||||
"borderWidth": 1
|
||||
}
|
||||
},
|
||||
"graph": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": 1,
|
||||
"color": "#aaaaaa"
|
||||
},
|
||||
"symbolSize": "5",
|
||||
"symbol": "circle",
|
||||
"smooth": true,
|
||||
"color": [
|
||||
"#4429e6",
|
||||
"#fd6280",
|
||||
"#28dbe2",
|
||||
"#ffc404",
|
||||
"#b92325",
|
||||
"#1b9f17",
|
||||
"#fe8705",
|
||||
"#97b552",
|
||||
"#95706d",
|
||||
"#dc69aa",
|
||||
"#07a2a4",
|
||||
"#9a7fd1",
|
||||
"#588dd5",
|
||||
"#f5994e",
|
||||
"#333333"
|
||||
],
|
||||
"label": {
|
||||
"color": "#fefefe"
|
||||
}
|
||||
},
|
||||
"map": {
|
||||
"itemStyle": {
|
||||
"areaColor": "#dddddd",
|
||||
"borderColor": "#eeeeee",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"label": {
|
||||
"color": "#d87a80"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"areaColor": "rgba(254,153,78,1)",
|
||||
"borderColor": "#444",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"label": {
|
||||
"color": "rgb(100,0,0)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"geo": {
|
||||
"itemStyle": {
|
||||
"areaColor": "#dddddd",
|
||||
"borderColor": "#eeeeee",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"label": {
|
||||
"color": "#d87a80"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"areaColor": "rgba(254,153,78,1)",
|
||||
"borderColor": "#444",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"label": {
|
||||
"color": "rgb(100,0,0)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"categoryAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": false,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"rgba(250,250,250,0.3)",
|
||||
"rgba(200,200,200,0.3)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"valueAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": true,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"#ffffff",
|
||||
"rgba(0,0,0,0.02)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"logAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": true,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"#ffffff",
|
||||
"rgba(0,0,0,0.02)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"rgba(250,250,250,0.3)",
|
||||
"rgba(200,200,200,0.3)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"toolbox": {
|
||||
"iconStyle": {
|
||||
"borderColor": "#000000"
|
||||
},
|
||||
"emphasis": {
|
||||
"iconStyle": {
|
||||
"borderColor": "#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"color": "#333333"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"axisPointer": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.3)",
|
||||
"width": "1"
|
||||
},
|
||||
"crossStyle": {
|
||||
"color": "rgba(0,0,0,0.3)",
|
||||
"width": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
"lineStyle": {
|
||||
"color": "#008acd",
|
||||
"width": 1
|
||||
},
|
||||
"itemStyle": {
|
||||
"color": "#008acd",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"controlStyle": {
|
||||
"color": "#008acd",
|
||||
"borderColor": "#008acd",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"checkpointStyle": {
|
||||
"color": "#2ec7c9",
|
||||
"borderColor": "#2ec7c9"
|
||||
},
|
||||
"label": {
|
||||
"color": "#008acd"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"color": "#a9334c"
|
||||
},
|
||||
"controlStyle": {
|
||||
"color": "#008acd",
|
||||
"borderColor": "#008acd",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"label": {
|
||||
"color": "#008acd"
|
||||
}
|
||||
}
|
||||
},
|
||||
"visualMap": {
|
||||
"color": [
|
||||
"#ffffff",
|
||||
"#4429e6"
|
||||
]
|
||||
},
|
||||
"dataZoom": {
|
||||
"backgroundColor": "rgba(47,69,84,0)",
|
||||
"dataBackgroundColor": "#efefff",
|
||||
"fillerColor": "rgba(182,162,222,0.2)",
|
||||
"handleColor": "#008acd",
|
||||
"handleSize": "100%",
|
||||
"textStyle": {
|
||||
"color": "#333333"
|
||||
}
|
||||
},
|
||||
"markPoint": {
|
||||
"label": {
|
||||
"color": "#fefefe"
|
||||
},
|
||||
"emphasis": {
|
||||
"label": {
|
||||
"color": "#fefefe"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
@@ -0,0 +1,409 @@
|
||||
{
|
||||
"color": [
|
||||
"#4429e6",
|
||||
"#fd6280",
|
||||
"#28dbe2",
|
||||
"#ffc404",
|
||||
"#b92325",
|
||||
"#1b9f17",
|
||||
"#fe8705",
|
||||
"#97b552",
|
||||
"#95706d",
|
||||
"#dc69aa",
|
||||
"#07a2a4",
|
||||
"#9a7fd1",
|
||||
"#588dd5",
|
||||
"#f5994e",
|
||||
"#333333"
|
||||
],
|
||||
"backgroundColor": "rgba(0,0,0,0)",
|
||||
"textStyle": {},
|
||||
"title": {
|
||||
"textStyle": {
|
||||
"color": "#333333"
|
||||
},
|
||||
"subtextStyle": {
|
||||
"color": "#999999"
|
||||
}
|
||||
},
|
||||
"line": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": "2"
|
||||
},
|
||||
"symbolSize": "5",
|
||||
"symbol": "circle",
|
||||
"smooth": true
|
||||
},
|
||||
"radar": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": "2"
|
||||
},
|
||||
"symbolSize": "5",
|
||||
"symbol": "circle",
|
||||
"smooth": true
|
||||
},
|
||||
"bar": {
|
||||
"itemStyle": {
|
||||
"barBorderWidth": "2",
|
||||
"barBorderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"pie": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"scatter": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"boxplot": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"parallel": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"sankey": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"funnel": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"gauge": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
}
|
||||
},
|
||||
"candlestick": {
|
||||
"itemStyle": {
|
||||
"color": "#d87a80",
|
||||
"color0": "#2ec7c9",
|
||||
"borderColor": "#d87a80",
|
||||
"borderColor0": "#2ec7c9",
|
||||
"borderWidth": 1
|
||||
}
|
||||
},
|
||||
"graph": {
|
||||
"itemStyle": {
|
||||
"borderWidth": "2",
|
||||
"borderColor": "rgba(255,255,255,0.3)"
|
||||
},
|
||||
"lineStyle": {
|
||||
"width": 1,
|
||||
"color": "#aaaaaa"
|
||||
},
|
||||
"symbolSize": "5",
|
||||
"symbol": "circle",
|
||||
"smooth": true,
|
||||
"color": [
|
||||
"#4429e6",
|
||||
"#fd6280",
|
||||
"#28dbe2",
|
||||
"#ffc404",
|
||||
"#b92325",
|
||||
"#1b9f17",
|
||||
"#fe8705",
|
||||
"#97b552",
|
||||
"#95706d",
|
||||
"#dc69aa",
|
||||
"#07a2a4",
|
||||
"#9a7fd1",
|
||||
"#588dd5",
|
||||
"#f5994e",
|
||||
"#333333"
|
||||
],
|
||||
"label": {
|
||||
"color": "#fefefe"
|
||||
}
|
||||
},
|
||||
"map": {
|
||||
"itemStyle": {
|
||||
"areaColor": "#dddddd",
|
||||
"borderColor": "#eeeeee",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"label": {
|
||||
"color": "#d87a80"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"areaColor": "rgba(254,153,78,1)",
|
||||
"borderColor": "#444",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"label": {
|
||||
"color": "rgb(100,0,0)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"geo": {
|
||||
"itemStyle": {
|
||||
"areaColor": "#dddddd",
|
||||
"borderColor": "#eeeeee",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"label": {
|
||||
"color": "#d87a80"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"areaColor": "rgba(254,153,78,1)",
|
||||
"borderColor": "#444",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"label": {
|
||||
"color": "rgb(100,0,0)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"categoryAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": false,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"rgba(250,250,250,0.3)",
|
||||
"rgba(200,200,200,0.3)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"valueAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": true,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"#ffffff",
|
||||
"rgba(0,0,0,0.02)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"logAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": true,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"#ffffff",
|
||||
"rgba(0,0,0,0.02)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.1)"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"show": true,
|
||||
"color": "#333"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"rgba(250,250,250,0.3)",
|
||||
"rgba(200,200,200,0.3)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"toolbox": {
|
||||
"iconStyle": {
|
||||
"borderColor": "#000000"
|
||||
},
|
||||
"emphasis": {
|
||||
"iconStyle": {
|
||||
"borderColor": "#000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"color": "#333333"
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"axisPointer": {
|
||||
"lineStyle": {
|
||||
"color": "rgba(0,0,0,0.3)",
|
||||
"width": "1"
|
||||
},
|
||||
"crossStyle": {
|
||||
"color": "rgba(0,0,0,0.3)",
|
||||
"width": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
"lineStyle": {
|
||||
"color": "#008acd",
|
||||
"width": 1
|
||||
},
|
||||
"itemStyle": {
|
||||
"color": "#008acd",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"controlStyle": {
|
||||
"color": "#008acd",
|
||||
"borderColor": "#008acd",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"checkpointStyle": {
|
||||
"color": "#2ec7c9",
|
||||
"borderColor": "#2ec7c9"
|
||||
},
|
||||
"label": {
|
||||
"color": "#008acd"
|
||||
},
|
||||
"emphasis": {
|
||||
"itemStyle": {
|
||||
"color": "#a9334c"
|
||||
},
|
||||
"controlStyle": {
|
||||
"color": "#008acd",
|
||||
"borderColor": "#008acd",
|
||||
"borderWidth": 0.5
|
||||
},
|
||||
"label": {
|
||||
"color": "#008acd"
|
||||
}
|
||||
}
|
||||
},
|
||||
"visualMap": {
|
||||
"color": [
|
||||
"#ffffff",
|
||||
"#4429e6"
|
||||
]
|
||||
},
|
||||
"dataZoom": {
|
||||
"backgroundColor": "rgba(47,69,84,0)",
|
||||
"dataBackgroundColor": "#efefff",
|
||||
"fillerColor": "rgba(182,162,222,0.2)",
|
||||
"handleColor": "#008acd",
|
||||
"handleSize": "100%",
|
||||
"textStyle": {
|
||||
"color": "#333333"
|
||||
}
|
||||
},
|
||||
"markPoint": {
|
||||
"label": {
|
||||
"color": "#fefefe"
|
||||
},
|
||||
"emphasis": {
|
||||
"label": {
|
||||
"color": "#fefefe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// 导入echarts核心模块
|
||||
import * as echarts from 'echarts/core'
|
||||
// 导入主题JSON
|
||||
import themeJson from './apipark-chart-palette.json'
|
||||
|
||||
// 全局注册主题
|
||||
export function registerApiparkTheme() {
|
||||
echarts.registerTheme('apipark', themeJson)
|
||||
}
|
||||
|
||||
// 导出主题名称,方便组件使用
|
||||
export const THEME_NAME = 'apipark'
|
||||
@@ -0,0 +1,11 @@
|
||||
// 导入主题配置
|
||||
import themeJson from './apipark-chart-palette.json'
|
||||
|
||||
// 导出主题配置
|
||||
export const apiparkTheme = themeJson
|
||||
|
||||
// 导出颜色列表,方便单独使用
|
||||
export const chartColors = themeJson.color
|
||||
|
||||
// 导出默认颜色
|
||||
export const defaultColor = chartColors[0]
|
||||
@@ -22,8 +22,10 @@ export const BreadcrumbProvider = ({ children }: unknown) => {
|
||||
<BreadcrumbContext.Provider
|
||||
value={{
|
||||
setBreadcrumb: (newItems) => {
|
||||
newItems.slice(0, newItems.length - 1).forEach((item) => {
|
||||
item.title = <span className="cursor-pointer hover:text-theme">{item.title}</span>
|
||||
newItems.forEach((item) => {
|
||||
item.title = (
|
||||
<span className={`${item.onClick ? 'cursor-pointer hover:text-theme' : ''}`}>{item.title}</span>
|
||||
)
|
||||
})
|
||||
setBreadcrumb(newItems)
|
||||
},
|
||||
|
||||
@@ -87,8 +87,8 @@ const mockData = [
|
||||
},
|
||||
{
|
||||
name: 'API 市场',
|
||||
key: 'serviceHub',
|
||||
path: '/serviceHub',
|
||||
key: 'portal',
|
||||
path: '/portal',
|
||||
icon: 'ic:baseline-hub',
|
||||
access: 'system.api_portal.api_portal.view'
|
||||
},
|
||||
@@ -107,15 +107,15 @@ const mockData = [
|
||||
},
|
||||
{
|
||||
name: '服务',
|
||||
key: 'analyticsSubscriber',
|
||||
path: '/analytics/subscriber/list',
|
||||
key: 'analyticsService',
|
||||
path: '/analytics/service/list',
|
||||
icon: 'ic:baseline-blinds-closed',
|
||||
access: 'system.analysis.run_view.view'
|
||||
},
|
||||
{
|
||||
name: '消费者',
|
||||
key: 'analyticsProvider',
|
||||
path: '/analytics/provider/list',
|
||||
key: 'analyticsConsumer',
|
||||
path: '/analytics/consumer/list',
|
||||
icon: 'ic:baseline-apps',
|
||||
access: 'system.analysis.run_view.view'
|
||||
},
|
||||
@@ -253,7 +253,7 @@ const mockData = [
|
||||
access: 'system.settings.ssl_certificate.view'
|
||||
},
|
||||
{
|
||||
name: '日志',
|
||||
name: '日志输出',
|
||||
key: 'logsettings',
|
||||
path: '/logsettings',
|
||||
icon: 'ic:baseline-sticky-note-2',
|
||||
|
||||
@@ -112,10 +112,10 @@ const mockData = {
|
||||
},
|
||||
{
|
||||
driver: 'apipark.builtIn.component',
|
||||
name: 'serviceHub',
|
||||
name: 'portal',
|
||||
router: [
|
||||
{
|
||||
path: 'serviceHub',
|
||||
path: 'portal',
|
||||
type: 'normal'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -54,6 +54,10 @@
|
||||
"上游列表": "K54e44357",
|
||||
"备注": "Kb8e8e6f5",
|
||||
"上线情况": "K7e52ffa3",
|
||||
"服务 ID": "K1e84ad04",
|
||||
"服务尚未发布": "Ke1e649cb",
|
||||
"跳转至详情页": "K2e683a7d",
|
||||
"暂无服务描述": "Ka4b45550",
|
||||
"申请原因": "K1ab0ae5b",
|
||||
"审核意见": "K53c00c3c",
|
||||
"暂无(0)权限,请联系管理员分配。": "Kfd50704d",
|
||||
@@ -114,6 +118,7 @@
|
||||
"无需审核:允许任何消费者调用该服务": "K1fc2cc28",
|
||||
"人工审核:仅允许通过人工审核的消费者调用该服务": "K8dabb98e",
|
||||
"开启:AI Agent 等产品能够通过 MCP 方式调用服务": "Ke959f135",
|
||||
"总览": "Kaf9e8011",
|
||||
"永久": "Kbfe02d7f",
|
||||
"否": "K1e9c479e",
|
||||
"是": "Kaddfcb6b",
|
||||
@@ -241,6 +246,7 @@
|
||||
"调用地址": "K2f5fdf5e",
|
||||
"消费者 IP": "K1bc5e0a3",
|
||||
"鉴权名称": "K6f39ea21",
|
||||
"日志输出": "K3c722abd",
|
||||
"暂无操作权限,请联系管理员分配。": "K23fda291",
|
||||
"微信小程序": "K4618cb0a",
|
||||
"获取文件,需填路径": "Ka854f511",
|
||||
@@ -345,11 +351,10 @@
|
||||
"重置": "K50d471b2",
|
||||
"查询": "Kee8ae330",
|
||||
"请输入 APIURL 搜索": "Kf8187c33",
|
||||
"服务": "Kb58e0c3f",
|
||||
"说明文档": "K6cd677b",
|
||||
"最近一次更新者": "K617f34f1",
|
||||
"最近一次更新时间": "K6ebca204",
|
||||
"保存": "Kabfe9512",
|
||||
"服务": "Kb58e0c3f",
|
||||
"API 路由": "K51d1eb5d",
|
||||
"API 文档": "Ka2b6d281",
|
||||
"使用说明": "Kdefa9caa",
|
||||
@@ -361,14 +366,10 @@
|
||||
"管理": "K5974bf24",
|
||||
"调用拓扑图": "K3fa5c4c3",
|
||||
"设置": "Kb5c7b82d",
|
||||
"服务 ID": "K1e84ad04",
|
||||
"新增订阅方": "K39ab0358",
|
||||
"手动添加": "K18307d56",
|
||||
"订阅申请": "K705fe9f5",
|
||||
"订阅方": "K3a67ea90",
|
||||
"API": "K3ba29a85",
|
||||
"编辑 API": "Ke93388fd",
|
||||
"添加 API": "K84aabfd4",
|
||||
"AI 路由设置": "Kefa2a4cf",
|
||||
"路由名称": "K66060758",
|
||||
"请求路径": "K5582ac8",
|
||||
@@ -379,7 +380,6 @@
|
||||
"拦截接口": "Kee4139c2",
|
||||
"开启拦截后,网关会拦截所有该路径的请求。": "K3e38ea",
|
||||
"模型配置": "K8a35059b",
|
||||
"路由": "Kf9dcef3a",
|
||||
"添加路由": "K6134bbe8",
|
||||
"输入 URL 查找路由": "Kf85b83a0",
|
||||
"线上模型": "K84b2cf2d",
|
||||
@@ -554,11 +554,15 @@
|
||||
"访客模式": "K192b3e38",
|
||||
"您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。": "K91aa4801",
|
||||
"Version (0)-(1)": "K480045ce",
|
||||
"日志配置": "Kadee8e49",
|
||||
"日志输出设置": "K74a5fbc0",
|
||||
"提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。": "K2724314b",
|
||||
"日志配置": "Kadee8e49",
|
||||
"MCP 配置": "K6e9c928f",
|
||||
"Open API 文档": "Kb6d0eb39",
|
||||
"AI 代理集成": "Ke6908f16",
|
||||
"请先订阅该服务": "K71ed51fa",
|
||||
"申请": "K4aa9ed2c",
|
||||
"选择 API Key": "K1bec8cbe",
|
||||
"新增 API Key": "Kb0e0aeda",
|
||||
"API 密钥可用于调用系统级 Open API 和 MCP。": "K9d81999c",
|
||||
"MCP 服务": "Kf106bc62",
|
||||
@@ -619,7 +623,7 @@
|
||||
"数据源": "K8fa58214",
|
||||
"设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。": "Kdbafd6f9",
|
||||
"统计图表": "K1358acf",
|
||||
"数据日志": "K17dc3a62",
|
||||
"请求日志": "Kc8bf447",
|
||||
"地址(IP:端口)": "K62dabdf6",
|
||||
"组织(Organization)": "K2db12335",
|
||||
"添加策略": "K34d0d409",
|
||||
@@ -627,8 +631,6 @@
|
||||
"处理日志": "Ke429194e",
|
||||
"脱敏前": "K8c34c02f",
|
||||
"脱敏后": "K8e3d388d",
|
||||
"编辑服务策略": "Kf06f6737",
|
||||
"添加服务策略": "K205971e1",
|
||||
"编辑策略": "Kc82b8374",
|
||||
"策略类型": "K4b34a5e5",
|
||||
"匹配条件": "K57f0fee8",
|
||||
@@ -660,6 +662,29 @@
|
||||
"系统级别角色": "K138facd3",
|
||||
"添加角色": "K6eac768d",
|
||||
"团队级别角色": "Kb9c2cf02",
|
||||
"API / Tools": "K9d526cac",
|
||||
"消费者": "K7acfcfad",
|
||||
"HTTP 状态": "Kc68ba0f4",
|
||||
"IP": "Kb09b747",
|
||||
"通过系统级别的 API Key 来调用": "K2eacb44f",
|
||||
"日志详情": "K764bca7c",
|
||||
"暂无数据": "Kf8525cf2",
|
||||
"输入 Token": "K33bc1ad1",
|
||||
"输出 Token": "Ke00ff18b",
|
||||
"总 Token": "K81140e5b",
|
||||
"订阅数量": "Ke04bc00d",
|
||||
"已开启": "K1b97ae0a",
|
||||
"开启 MCP": "K19ec733b",
|
||||
"API 使用排名": "Kbee2340",
|
||||
"消费者使用排名": "Kf6af1f40",
|
||||
"请求数": "K318a7519",
|
||||
"流量": "K53eb7414",
|
||||
"平均响应时间": "K7c8d5c23",
|
||||
"平均请求数": "K652843b0",
|
||||
"平均流量": "K8158a6e4",
|
||||
"Token": "K9ef68e3f",
|
||||
"平均 Token/s 统计": "K6c016898",
|
||||
"平均 Token/订阅者统计": "Kf5eeb9c5",
|
||||
"单位:ms,最小值:1": "K2a16c93b",
|
||||
"API 路由设置": "Ka945cfb1",
|
||||
"API 基础信息": "K2e050340",
|
||||
@@ -742,7 +767,6 @@
|
||||
"退出全屏": "Kaf70c3b",
|
||||
"(0)调用详情": "Kd22841a4",
|
||||
"消费者调用统计": "K61cca533",
|
||||
"消费者": "K7acfcfad",
|
||||
"请选择消费者": "Kdfff59d4",
|
||||
"调用趋势": "K8c7f2d2e",
|
||||
"(0)-(1)调用趋势": "K657c3452",
|
||||
@@ -783,14 +807,13 @@
|
||||
"配置集群信息": "Ke5ed9810",
|
||||
"监控设置": "K1a132228",
|
||||
"配置监控信息": "K6af08c3c",
|
||||
"监控总览": "K4a1a14",
|
||||
"服务被调用统计": "K69741ea7",
|
||||
"API 调用统计": "K9c8d9933",
|
||||
"加载数据失败,请重试": "K6c2d93b6",
|
||||
"亿": "K145e4941",
|
||||
"万": "Ke6a935d",
|
||||
"搜索分类或标签": "Kd59290a2",
|
||||
"暂无API数据": "K6b75bdbc",
|
||||
"搜索或选择消费者": "Kb684c806",
|
||||
"该消费者已订阅": "K5611e01e",
|
||||
"申请理由": "K4b15d6f5",
|
||||
"支持把当前服务对接主流的 AI Agent平台,实现在 Agent 平台上快速、安全和合规地使用企业开放的 API 能力。": "K2ec0fa56",
|
||||
"可按以下步骤进行对接:": "K35f23b64",
|
||||
@@ -850,8 +873,7 @@
|
||||
"版本": "K81634069",
|
||||
"更新时间": "Keefda53d",
|
||||
"介绍": "K59cdbec3",
|
||||
"暂无服务描述": "Ka4b45550",
|
||||
"申请": "K4aa9ed2c",
|
||||
"API": "K3ba29a85",
|
||||
"无标签": "K96a2f1c8",
|
||||
"分类": "Kb32f0afe",
|
||||
"服务市场": "K370a3eb2",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"Kb58e0c3f": "Service",
|
||||
"Kc9e489f5": "Team",
|
||||
"K61c89f5f": "API Portal",
|
||||
"K16d71239": "Analysis",
|
||||
"K16d71239": "Analytics",
|
||||
"K714c192d": "Call Statistics",
|
||||
"Kd57dfe97": "Topology",
|
||||
"K3fe97dcc": "System Settings",
|
||||
@@ -186,7 +186,7 @@
|
||||
"K617f34f1": "Updated By",
|
||||
"K6ebca204": "Update Time",
|
||||
"Kabfe9512": "Save",
|
||||
"K51d1eb5d": "API",
|
||||
"K51d1eb5d": "API Routes",
|
||||
"Ka2b6d281": "API Docs",
|
||||
"Kdefa9caa": "Usage Instructions",
|
||||
"K36856e71": "Publish",
|
||||
@@ -210,7 +210,7 @@
|
||||
"K469e475a": "Max Retry Times",
|
||||
"K8a35059b": "Model Settings",
|
||||
"Kf9dcef3a": "API",
|
||||
"K6134bbe8": "Add API",
|
||||
"K6134bbe8": "Add API Route",
|
||||
"Kf85b83a0": "Enter URL to Search",
|
||||
"Kcf9f90b8": "Model Provider",
|
||||
"Kfede1c7c": "Model",
|
||||
@@ -927,5 +927,42 @@
|
||||
"K71ed51fa": "Please subscribe to the service first",
|
||||
"K1bec8cbe": "Select API Key",
|
||||
"K5611e01e": "This consumer is already subscribed",
|
||||
"Kaf9e8011": "Overview"
|
||||
"Kaf9e8011": "Overview",
|
||||
"Ke1e649cb": "Service not released",
|
||||
"K2e683a7d": "Open in Portal",
|
||||
"Ke04bc00d": "Subscribers",
|
||||
"K1b97ae0a": "Enabled",
|
||||
"K19ec733b": "Enable MCP",
|
||||
"Kbee2340": "Top API",
|
||||
"Kf6af1f40": "Top Consumer",
|
||||
"K318a7519": "Requests",
|
||||
"K9ef68e3f": "Token",
|
||||
"Kfb14ccb0": "Models",
|
||||
"K10a8bee3": "Avg Token per Second",
|
||||
"K2727b76b": "Avg Requests per Subscriber",
|
||||
"K4c7a6704": "Avg Token per Subscriber",
|
||||
"K53eb7414": "Traffic",
|
||||
"K7c8d5c23": "Avg Response Time",
|
||||
"Kf9eb702": "QRS",
|
||||
"K7f0aa740": "Avg Traffic per Subscriber",
|
||||
"K9d526cac": "API / Tools",
|
||||
"Kc68ba0f4": "HTTP Status",
|
||||
"Kb09b747": "IP",
|
||||
"K2eacb44f": "Request the API using a system-level API Key",
|
||||
"K764bca7c": "Log Detail",
|
||||
"K6c016898": "Avg Token per Second",
|
||||
"K652843b0": "Avg Requests per Subscriber",
|
||||
"Kdbf831a0": "Avg Token per Subscriber",
|
||||
"K8158a6e4": "Avg Traffic per Subscriber",
|
||||
"K6b882d4a": "Avg Token per Subscriber",
|
||||
"K6c2d93b6": "Failed to load data, please try again",
|
||||
"Kf5eeb9c5": "Avg Token per Subscriber",
|
||||
"K1639a17a": "API Routes Docs",
|
||||
"K33bc1ad1": "Input Token",
|
||||
"Ke00ff18b": "Output Token",
|
||||
"K81140e5b": "Total Token",
|
||||
"K3c722abd": "Log Output",
|
||||
"K74a5fbc0": "Log Output Settings",
|
||||
"Kc8bf447": "Request Log",
|
||||
"Kf8525cf2": "No Data"
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
"K617f34f1": "更新者",
|
||||
"K6ebca204": "更新日時",
|
||||
"Kabfe9512": "保存",
|
||||
"K51d1eb5d": "API",
|
||||
"K51d1eb5d": "APIルート",
|
||||
"Ka2b6d281": "API ドキュメント",
|
||||
"Kdefa9caa": "説明ドキュメント",
|
||||
"K36856e71": "公開",
|
||||
@@ -213,7 +213,7 @@
|
||||
"K469e475a": "リトライ回数",
|
||||
"K8a35059b": "モデル設定",
|
||||
"Kf9dcef3a": "API",
|
||||
"K6134bbe8": "API を追加",
|
||||
"K6134bbe8": "APIルートを追加する",
|
||||
"Kf85b83a0": "URL を入力して検索",
|
||||
"Kcf9f90b8": "モデルプロバイダー",
|
||||
"Kfede1c7c": "モデル",
|
||||
@@ -949,5 +949,42 @@
|
||||
"K71ed51fa": "このサービスに先にサブスクリプションしてください",
|
||||
"K1bec8cbe": "APIキーを選択してください",
|
||||
"K5611e01e": "この消費者はすでに購読しています",
|
||||
"Kaf9e8011": "概要"
|
||||
"Kaf9e8011": "概要",
|
||||
"Ke1e649cb": "サービスはまだ公開されていません",
|
||||
"K2e683a7d": "詳細ページへ移動",
|
||||
"Ke04bc00d": "サブスクリプション数",
|
||||
"K1b97ae0a": "有効",
|
||||
"K19ec733b": "MCP を有効にする",
|
||||
"Kbee2340": "API 使用ランキング",
|
||||
"Kf6af1f40": "コンシューマー使用ランキング",
|
||||
"K318a7519": "リクエスト数",
|
||||
"K9ef68e3f": "トークン",
|
||||
"Kfb14ccb0": "モデル使用量",
|
||||
"K10a8bee3": "平均トークン消費量",
|
||||
"K2727b76b": "ユーザーあたり平均リクエスト数",
|
||||
"K4c7a6704": "ユーザーあたり平均トークン消費量",
|
||||
"K53eb7414": "トラフィック",
|
||||
"K7c8d5c23": "平均応答時間",
|
||||
"Kf9eb702": "毎秒リクエスト数",
|
||||
"K7f0aa740": "ユーザーあたり平均トラフィック",
|
||||
"K9d526cac": "API / ツール",
|
||||
"Kc68ba0f4": "HTTP ステータス",
|
||||
"Kb09b747": "IP",
|
||||
"K2eacb44f": "システムレベルの API Key で呼び出し",
|
||||
"K764bca7c": "ログ詳細",
|
||||
"K6c016898": "平均トークン/s 統計",
|
||||
"K652843b0": "平均リクエスト数",
|
||||
"Kdbf831a0": "平均トークン/加入者 統計",
|
||||
"K8158a6e4": "平均トラフィック",
|
||||
"K6b882d4a": "平均トークン/加入者",
|
||||
"K6c2d93b6": "データの読み込みに失敗しました。もう一度お試しください",
|
||||
"Kf5eeb9c5": "平均トークン/加入者 統計",
|
||||
"K1639a17a": "APIルートのドキュメント",
|
||||
"K33bc1ad1": "入力トークン",
|
||||
"Ke00ff18b": "出力トークン",
|
||||
"K81140e5b": "合計トークン",
|
||||
"K3c722abd": "ログ出力",
|
||||
"K74a5fbc0": "ログ出力設定",
|
||||
"Kc8bf447": "リクエストログ",
|
||||
"Kf8525cf2": "データがありません"
|
||||
}
|
||||
|
||||
@@ -1,69 +1 @@
|
||||
{
|
||||
"K630c9e6d": "APIPark",
|
||||
"Ka3e9f580": "发布名称",
|
||||
"Kb2480682": "策略列表",
|
||||
"K76036e25": "HTTP 请求头",
|
||||
"K44607e3f": "全等匹配",
|
||||
"Kc287500a": "前缀匹配",
|
||||
"Kfc0b1147": "后缀匹配",
|
||||
"Ka4a92043": "子串匹配",
|
||||
"K30b2e44f": "非等匹配",
|
||||
"Kb1587991": "空值匹配",
|
||||
"K1e97dbd8": "存在匹配",
|
||||
"Kc8ee3e62": "不存在匹配",
|
||||
"K87c5a801": "区分大小写的正则匹配",
|
||||
"K95f062f1": "不区分大小写的正则匹配",
|
||||
"Kfbd230a5": "任意匹配",
|
||||
"Kd85208a3": "驳回",
|
||||
"Kad6aa439": "已订阅",
|
||||
"K9a68443b": "取消申请",
|
||||
"Kaeba0229": "透传客户端请求 Host",
|
||||
"K6d7e2fd0": "使用上游服务 Host",
|
||||
"K31332633": "重写 Host",
|
||||
"K2c2bc64f": "动态服务发现",
|
||||
"K78b1ca25": "地址",
|
||||
"K1644b775": "新增",
|
||||
"Kec91f0db": "申请方消费者",
|
||||
"K118d8d74": "数据格式",
|
||||
"Kfe7c7d2d": "关键字",
|
||||
"K2f57a694": "正则表达式",
|
||||
"K8953e0a6": "手机号",
|
||||
"K6f86a038": "身份证号",
|
||||
"K7954e7c8": "银行卡号",
|
||||
"K320fdb17": "金额",
|
||||
"K7867acda": "日期",
|
||||
"K7d327ae8": "局部显示",
|
||||
"Kfbf38e3c": "局部遮蔽",
|
||||
"Kd8c1fbb0": "截取",
|
||||
"K89829921": "替换",
|
||||
"K480a7165": "乱序",
|
||||
"Kea0d69df": "随机字符串",
|
||||
"Ke7c84d1d": "自定义字符串",
|
||||
"K49731763": "请输入IP地址或CIDR范围,每条以换行分割",
|
||||
"K3a34d49b": "待更新",
|
||||
"Kd2850420": "待删除",
|
||||
"K83237c89": "输入的IP或CIDR不符合格式",
|
||||
"K5ae2c87a": "请正确输入路径,如/usr/*或*/usr/*",
|
||||
"K67f4e9bb": "与外部平台集成时,获取 API 市场中文档信息的域名",
|
||||
"Kc82b8374": "编辑策略",
|
||||
"K4b34a5e5": "策略类型",
|
||||
"K57f0fee8": "匹配条件",
|
||||
"K10650c58": "数据脱敏规则",
|
||||
"K1b34a9ab": "配置脱敏规则",
|
||||
"K26d22405": "匹配值",
|
||||
"K1546e1fe": "脱敏类型",
|
||||
"K9b9b0629": "起始位置",
|
||||
"K52c84fe1": "长度",
|
||||
"Kde84409c": "替换类型",
|
||||
"K338653b4": "替换值",
|
||||
"Kbaeed3b7": "JSON Path",
|
||||
"K4cd91d61": "脱敏规则",
|
||||
"K8dcad979": "自定义字符串; 值:",
|
||||
"K82e3f7b7": "起始位置:(0)位;长度:(1)位",
|
||||
"K49dfc123": "已选择(0)项(1)数据",
|
||||
"K8457ea34": "所有(0)",
|
||||
"K7ca9a795": "属性名称",
|
||||
"Kc4391744": "属性值",
|
||||
"K678e13fc": "配置(0)",
|
||||
"Kf5fd27ed": "输入名称查找用户"
|
||||
}
|
||||
{}
|
||||
@@ -189,7 +189,7 @@
|
||||
"K617f34f1": "更新者",
|
||||
"K6ebca204": "更新时间",
|
||||
"Kabfe9512": "保存",
|
||||
"K51d1eb5d": "API",
|
||||
"K51d1eb5d": "API 路由",
|
||||
"Ka2b6d281": "API 文档",
|
||||
"Kdefa9caa": "说明文档",
|
||||
"K36856e71": "发布",
|
||||
@@ -213,7 +213,7 @@
|
||||
"K469e475a": "最大重试次数",
|
||||
"K8a35059b": "模型设置",
|
||||
"Kf9dcef3a": "API",
|
||||
"K6134bbe8": "添加 API",
|
||||
"K6134bbe8": "添加 API 路由",
|
||||
"Kf85b83a0": "输入 URL 查找",
|
||||
"Kcf9f90b8": "模型供应商",
|
||||
"Kfede1c7c": "模型",
|
||||
@@ -880,5 +880,40 @@
|
||||
"K71ed51fa": "请先订阅该服务",
|
||||
"K1bec8cbe": "选择 API Key",
|
||||
"K5611e01e": "该消费者已订阅",
|
||||
"Kaf9e8011": "总览"
|
||||
"Kaf9e8011": "总览",
|
||||
"Ke1e649cb": "服务尚未发布",
|
||||
"K2e683a7d": "跳转至详情页",
|
||||
"Ke04bc00d": "订阅数量",
|
||||
"K1b97ae0a": "已开启",
|
||||
"K19ec733b": "开启 MCP",
|
||||
"Kbee2340": "API 使用排名",
|
||||
"Kf6af1f40": "消费者使用排名",
|
||||
"K318a7519": "请求数",
|
||||
"K9ef68e3f": "Token",
|
||||
"Kfb14ccb0": "模型使用量",
|
||||
"K10a8bee3": "平均 Token 消耗",
|
||||
"K2727b76b": "人均请求数",
|
||||
"K4c7a6704": "人均 Token 消耗",
|
||||
"K53eb7414": "流量",
|
||||
"K7c8d5c23": "平均响应时间",
|
||||
"Kf9eb702": "每秒请求数量",
|
||||
"K7f0aa740": "人均流量",
|
||||
"K9d526cac": "API / Tools",
|
||||
"Kc68ba0f4": "HTTP 状态",
|
||||
"Kb09b747": "IP",
|
||||
"K2eacb44f": "通过系统级别的 API Key 来调用",
|
||||
"K764bca7c": "日志详情",
|
||||
"K6c016898": "平均 Token/s 统计",
|
||||
"K652843b0": "平均请求数",
|
||||
"K8158a6e4": "平均流量",
|
||||
"K6c2d93b6": "加载数据失败,请重试",
|
||||
"Kf5eeb9c5": "平均 Token/订阅者统计",
|
||||
"K1639a17a": "API 路由文档",
|
||||
"K33bc1ad1": "输入 Token",
|
||||
"Ke00ff18b": "输出 Token",
|
||||
"K81140e5b": "总 Token",
|
||||
"K3c722abd": "日志输出",
|
||||
"K74a5fbc0": "日志输出设置",
|
||||
"Kc8bf447": "请求日志",
|
||||
"Kf8525cf2": "暂无数据"
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
"K617f34f1": "更新者",
|
||||
"K6ebca204": "更新時間",
|
||||
"Kabfe9512": "保存",
|
||||
"K51d1eb5d": "API",
|
||||
"K51d1eb5d": "API 路由",
|
||||
"Ka2b6d281": "API 文檔",
|
||||
"Kdefa9caa": "說明文檔",
|
||||
"K36856e71": "發布",
|
||||
@@ -213,7 +213,7 @@
|
||||
"K469e475a": "最大重試次數",
|
||||
"K8a35059b": "模型設置",
|
||||
"Kf9dcef3a": "API",
|
||||
"K6134bbe8": "添加 API",
|
||||
"K6134bbe8": "添加 API 路由",
|
||||
"Kf85b83a0": "輸入 URL 查找",
|
||||
"Kcf9f90b8": "模型供應商",
|
||||
"Kfede1c7c": "模型",
|
||||
@@ -949,5 +949,42 @@
|
||||
"K71ed51fa": "請先訂閱該服務",
|
||||
"K1bec8cbe": "選擇 API Key",
|
||||
"K5611e01e": "該消費者已訂閱",
|
||||
"Kaf9e8011": "總覽"
|
||||
"Kaf9e8011": "總覽",
|
||||
"Ke1e649cb": "服務尚未發布",
|
||||
"K2e683a7d": "跳轉至詳情頁",
|
||||
"Ke04bc00d": "訂閱數量",
|
||||
"K1b97ae0a": "已開啟",
|
||||
"K19ec733b": "開啟 MCP",
|
||||
"Kbee2340": "API 使用排名",
|
||||
"Kf6af1f40": "消費者使用排名",
|
||||
"K318a7519": "請求數",
|
||||
"K9ef68e3f": "Token",
|
||||
"Kfb14ccb0": "模型使用量",
|
||||
"K10a8bee3": "平均 Token 消耗",
|
||||
"K2727b76b": "人均請求數",
|
||||
"K4c7a6704": "人均 Token 消耗",
|
||||
"K53eb7414": "流量",
|
||||
"K7c8d5c23": "平均回應時間",
|
||||
"Kf9eb702": "每秒請求數量",
|
||||
"K7f0aa740": "人均流量",
|
||||
"K9d526cac": "API / 工具",
|
||||
"Kc68ba0f4": "HTTP 狀態",
|
||||
"Kb09b747": "IP",
|
||||
"K2eacb44f": "透過系統級 API Key 調用",
|
||||
"K764bca7c": "日誌詳情",
|
||||
"K6c016898": "平均 Token/s 統計",
|
||||
"K652843b0": "平均請求數",
|
||||
"Kdbf831a0": "每位訂閱者平均 Token 統計",
|
||||
"K8158a6e4": "平均流量",
|
||||
"K6b882d4a": "每位訂閱者平均 Token",
|
||||
"K6c2d93b6": "載入資料失敗,請重試",
|
||||
"Kf5eeb9c5": "每位訂閱者平均 Token 統計",
|
||||
"K1639a17a": "API 路由文件",
|
||||
"K33bc1ad1": "輸入 Token",
|
||||
"Ke00ff18b": "輸出 Token",
|
||||
"K81140e5b": "總計 Token",
|
||||
"K3c722abd": "日誌輸出",
|
||||
"K74a5fbc0": "日誌輸出設定",
|
||||
"Kc8bf447": "請求日誌",
|
||||
"Kf8525cf2": "暫無資料"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { PluginEventHubProvider } from '@common/contexts/PluginEventHubContext'
|
||||
import { PluginSlotHubProvider } from '@common/contexts/PluginSlotHubContext'
|
||||
import useInitializeMonaco from '@common/hooks/useInitializeMonaco'
|
||||
import { $t } from '@common/locales'
|
||||
import { registerApiparkTheme } from '@common/const/charts/initChartTheme'
|
||||
import RenderRoutes from '@core/components/aoplatform/RenderRoutes'
|
||||
import { App as AppAntd, ConfigProvider } from 'antd'
|
||||
import { useMemo } from 'react'
|
||||
@@ -130,6 +131,9 @@ const antdComponentThemeToken = {
|
||||
}
|
||||
}
|
||||
|
||||
// 注册 ECharts 主题
|
||||
registerApiparkTheme()
|
||||
|
||||
function App() {
|
||||
const { locale } = useLocaleContext()
|
||||
useInitializeMonaco()
|
||||
|
||||
@@ -6,17 +6,18 @@ import { AiServiceRouterTableListItem, VariableItems } from './type'
|
||||
import { PageProColumns } from '@common/components/aoplatform/PageList'
|
||||
|
||||
export const AI_SERVICE_ROUTER_TABLE_COLUMNS: PageProColumns<AiServiceRouterTableListItem>[] = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'URL',
|
||||
dataIndex: 'requestPath',
|
||||
ellipsis: true,
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '模型',
|
||||
dataIndex: ['model', 'name'],
|
||||
|
||||
@@ -15,7 +15,7 @@ export type AiServiceConfigFieldType = {
|
||||
logoFile?:UploadFile;
|
||||
tags?:Array<string>;
|
||||
description?: string;
|
||||
team?:string;
|
||||
team?:EntityItem;
|
||||
master?:string;
|
||||
serviceType?:'public'|'inner';
|
||||
catalogue?:string | string[];
|
||||
|
||||
@@ -96,6 +96,20 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
|
||||
key: 'restServiceInside',
|
||||
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsidePage.tsx')),
|
||||
children: [
|
||||
{
|
||||
path: 'overview',
|
||||
key: 'restServiceInsideOverview',
|
||||
lazy: lazy(
|
||||
() => import(/* webpackChunkName: "[request]" */ '@core/pages/serviceOverview/RestServiceContainer')
|
||||
)
|
||||
},
|
||||
{
|
||||
path: 'logs',
|
||||
key: 'restServiceInsideLogs',
|
||||
lazy: lazy(
|
||||
() => import(/* webpackChunkName: "[request]" */ '@core/pages/serviceLogs/RestServiceLogsContainer')
|
||||
)
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
key: 'restServiceInsideApi',
|
||||
@@ -268,6 +282,20 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
|
||||
() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsidePage.tsx')
|
||||
),
|
||||
children: [
|
||||
{
|
||||
path: 'overview',
|
||||
key: 'aiServiceInsideOverview',
|
||||
lazy: lazy(
|
||||
() => import(/* webpackChunkName: "[request]" */ '@core/pages/serviceOverview/AiServiceContainer')
|
||||
)
|
||||
},
|
||||
{
|
||||
path: 'logs',
|
||||
key: 'aiServiceInsideLogs',
|
||||
lazy: lazy(
|
||||
() => import(/* webpackChunkName: "[request]" */ '@core/pages/serviceLogs/AiServiceLogsContainer')
|
||||
)
|
||||
},
|
||||
{
|
||||
path: 'api',
|
||||
key: 'aiServiceInsideApi',
|
||||
@@ -507,7 +535,7 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
|
||||
],
|
||||
|
||||
[
|
||||
'serviceHub',
|
||||
'portal',
|
||||
{
|
||||
type: 'module',
|
||||
component: <Outlet />,
|
||||
@@ -674,12 +702,12 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
|
||||
children: [
|
||||
{
|
||||
path: 'total',
|
||||
key: 'analytics2',
|
||||
key: 'analyticsTotal',
|
||||
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardTotal.tsx'))
|
||||
},
|
||||
{
|
||||
path: ':dashboardType',
|
||||
key: 'analytics3',
|
||||
key: 'analyticsOther',
|
||||
component: <Outlet />,
|
||||
children: [
|
||||
{
|
||||
|
||||
@@ -123,6 +123,7 @@ export type PartitionDataLogHeaderListFieldType = {
|
||||
export type PartitionDataLogConfigFieldType = {
|
||||
headers: PartitionDataLogHeaderListFieldType[]
|
||||
url: string
|
||||
driver?: string
|
||||
}
|
||||
|
||||
export const PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS: PageProColumns<PartitionDataLogConfigFieldType & { _id: string }>[] = [
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from './type'
|
||||
|
||||
import { PageProColumns } from '@common/components/aoplatform/PageList'
|
||||
import { LogItem } from '@core/pages/serviceLogs/ServiceLogs'
|
||||
|
||||
export enum SubscribeEnum {
|
||||
Rejected = 0,
|
||||
@@ -241,6 +242,12 @@ export const MATCH_CONFIG: ConfigField<MatchItem>[] = [
|
||||
]
|
||||
|
||||
export const SYSTEM_API_TABLE_COLUMNS: PageProColumns<SystemApiTableListItem>[] = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'URL',
|
||||
dataIndex: 'requestPath',
|
||||
@@ -500,3 +507,124 @@ export const SYSTEM_PUBLISH_ONLINE_COLUMNS = [
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
/** AI 服务排行 */
|
||||
export const AI_SERVICE_TOP_RANKING_LIST: PageProColumns<any>[] = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '请求总数',
|
||||
dataIndex: 'request',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Token',
|
||||
dataIndex: 'token',
|
||||
ellipsis: true
|
||||
}
|
||||
]
|
||||
|
||||
/** REST 服务排行 */
|
||||
export const REST_SERVICE_TOP_RANKING_LIST: PageProColumns<any>[] = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '请求总数',
|
||||
dataIndex: 'request',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '流量',
|
||||
dataIndex: 'traffic',
|
||||
ellipsis: true
|
||||
}
|
||||
]
|
||||
|
||||
/** REST 服务日志 */
|
||||
export const REST_SERVICE_LOG_LIST: PageProColumns<LogItem>[] = [
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'logTime',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'API / Tools',
|
||||
dataIndex: ['api', 'name'],
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '消费者',
|
||||
dataIndex: ['consumers', 'name'],
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'HTTP 状态',
|
||||
dataIndex: 'status',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'IP',
|
||||
dataIndex: 'ip',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '响应时间',
|
||||
dataIndex: 'responseTime',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '流量',
|
||||
dataIndex: 'traffic',
|
||||
ellipsis: true
|
||||
}
|
||||
]
|
||||
|
||||
/** AI 服务日志 */
|
||||
export const AI_SERVICE_LOG_LIST: PageProColumns<LogItem>[] = [
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'logTime',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'API / Tools',
|
||||
dataIndex: ['api', 'name'],
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '消费者',
|
||||
dataIndex: ['consumer', 'name'],
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'HTTP 状态',
|
||||
dataIndex: 'status',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '模型',
|
||||
dataIndex: 'model',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'IP',
|
||||
dataIndex: 'ip',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Token/s',
|
||||
dataIndex: 'tokenPerSecond',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'Token',
|
||||
dataIndex: 'token',
|
||||
ellipsis: true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -97,6 +97,7 @@ export type SystemApiProxyType = {
|
||||
export type SystemApiProxyFieldType = {
|
||||
protocols: string[];
|
||||
id:string;
|
||||
name:string
|
||||
description?:string;
|
||||
disable:boolean;
|
||||
path:string;
|
||||
|
||||
@@ -1156,6 +1156,22 @@ p{
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ranking-list .ant-pro-table{
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
border: none !important;
|
||||
}
|
||||
.ranking-list .ant-table-tbody:not(tbody) .ant-table-cell{
|
||||
padding: 10px 10px !important;
|
||||
}
|
||||
.ranking-list .ant-table-container .ant-table-thead th{
|
||||
background-color: #fff !important;
|
||||
padding: 10px 10px !important;
|
||||
}
|
||||
.ranking-list .ant-table-container .ant-table-thead th::before{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-alert-info{
|
||||
background: #1784FC1A !important;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@ import { App, Button } from 'antd'
|
||||
import { EntityItem } from '@common/const/type.ts'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { $t } from '@common/locales'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
|
||||
const ServiceInsideDocument = () => {
|
||||
const { message } = App.useApp()
|
||||
const [updater, setUpdater] = useState<string>()
|
||||
@@ -19,8 +18,6 @@ const ServiceInsideDocument = () => {
|
||||
const [doc, setDoc] = useState<string>()
|
||||
const { fetchData } = useFetch()
|
||||
const { serviceId, teamId } = useParams<RouterParams>()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const navigator = useNavigate()
|
||||
|
||||
const save = () => {
|
||||
fetchData<
|
||||
@@ -80,15 +77,6 @@ const ServiceInsideDocument = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('使用说明')
|
||||
}
|
||||
])
|
||||
getServiceDoc()
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -9,11 +9,12 @@ import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import { AiServiceConfigFieldType } from '@core/const/ai-service/type.ts'
|
||||
import { App, Menu, MenuProps } from 'antd'
|
||||
import { ItemType, MenuItemGroupType, MenuItemType } from 'antd/es/menu/interface'
|
||||
import Paragraph from 'antd/es/typography/Paragraph'
|
||||
import ServiceInfoCard from '@common/components/aoplatform/serviceInfoCard.tsx'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { FC, useEffect, useMemo, useState } from 'react'
|
||||
import { Link, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
import { useAiServiceContext } from '../../contexts/AiServiceContext.tsx'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
const APP_MODE = import.meta.env.VITE_APP_MODE
|
||||
|
||||
const AiServiceInsidePage: FC = () => {
|
||||
@@ -27,6 +28,7 @@ const AiServiceInsidePage: FC = () => {
|
||||
const [activeMenu, setActiveMenu] = useState<string>()
|
||||
const navigateTo = useNavigate()
|
||||
const [showMenu, setShowMenu] = useState<boolean>(false)
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
|
||||
const getAiServiceInfo = () => {
|
||||
fetchData<BasicResponse<{ service: AiServiceConfigFieldType }>>('service/info', {
|
||||
@@ -67,6 +69,7 @@ const AiServiceInsidePage: FC = () => {
|
||||
'assets',
|
||||
null,
|
||||
[
|
||||
getItem(<Link to="./overview">{$t('总览')}</Link>, 'overview', undefined, undefined, undefined, ''),
|
||||
getItem(
|
||||
<Link to="./route">{$t('API 路由')}</Link>,
|
||||
'route',
|
||||
@@ -149,7 +152,8 @@ const AiServiceInsidePage: FC = () => {
|
||||
'project.myAiService.topology.view'
|
||||
)
|
||||
: null,
|
||||
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting', undefined, undefined, undefined, '')
|
||||
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting', undefined, undefined, undefined, ''),
|
||||
getItem(<Link to="./logs">{$t('日志')}</Link>, 'logs', undefined, undefined, undefined, '')
|
||||
],
|
||||
'group'
|
||||
)
|
||||
@@ -202,7 +206,7 @@ const AiServiceInsidePage: FC = () => {
|
||||
} else if (serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]) {
|
||||
setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1])
|
||||
} else {
|
||||
setActiveMenu('route')
|
||||
setActiveMenu('overview')
|
||||
}
|
||||
}, [currentUrl])
|
||||
|
||||
@@ -213,10 +217,19 @@ const AiServiceInsidePage: FC = () => {
|
||||
}, [accessData])
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigateTo('/service/list')
|
||||
},
|
||||
{
|
||||
title: aiServiceInfo?.name || ''
|
||||
}
|
||||
])
|
||||
if (activeMenu && serviceId === currentUrl.split('/')[currentUrl.split('/').length - 1]) {
|
||||
navigateTo(`/service/${teamId}/aiInside/${serviceId}/${activeMenu}`)
|
||||
}
|
||||
}, [activeMenu])
|
||||
}, [activeMenu, state.language, aiServiceInfo])
|
||||
|
||||
useEffect(() => {
|
||||
serviceId && getAiServiceInfo()
|
||||
@@ -231,17 +244,8 @@ const AiServiceInsidePage: FC = () => {
|
||||
{showMenu ? (
|
||||
<InsidePage
|
||||
pageTitle={aiServiceInfo?.name || '-'}
|
||||
tagList={[
|
||||
...(aiServiceInfo?.enable_mcp ? [{ label: 'MCP', color: '#FFF0C1', className: 'text-[#000]' }] : []),
|
||||
{
|
||||
label: (
|
||||
<Paragraph className="mb-0" copyable={serviceId ? { text: serviceId } : false}>
|
||||
{$t('服务 ID')}:{serviceId || '-'}
|
||||
</Paragraph>
|
||||
)
|
||||
}
|
||||
]}
|
||||
backUrl="/service/list"
|
||||
customBanner={<ServiceInfoCard serviceId={serviceId} teamId={teamId} />}
|
||||
>
|
||||
<div className="flex flex-1 h-full">
|
||||
<Menu
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import {ActionType} from "@ant-design/pro-components";
|
||||
import {FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react";
|
||||
import {Link, useNavigate, useParams} from "react-router-dom";
|
||||
import {useParams} from "react-router-dom";
|
||||
import {App, Form,TreeSelect} from "antd";
|
||||
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx";
|
||||
import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx";
|
||||
@@ -18,7 +17,6 @@ import { checkAccess } from "@common/utils/permission.ts";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
|
||||
const AiServiceInsideSubscriber:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal,message } = App.useApp()
|
||||
const {fetchData} = useFetch()
|
||||
const {serviceId, teamId} = useParams<RouterParams>()
|
||||
@@ -26,7 +24,6 @@ const AiServiceInsideSubscriber:FC = ()=>{
|
||||
const pageListRef = useRef<ActionType>(null);
|
||||
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
|
||||
const {accessData,state} = useGlobalContext()
|
||||
const navigator = useNavigate()
|
||||
const getAiServiceSubscriber = ()=>{
|
||||
return fetchData<BasicResponse<{subscribers:AiServiceSubscriberTableListItem[]}>>('service/subscribers',{method:'GET',eoParams:{service:serviceId,team:teamId},eoTransformKeys:['apply_time']}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
@@ -120,15 +117,6 @@ const AiServiceInsideSubscriber:FC = ()=>{
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('订阅方管理')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
@@ -6,33 +6,21 @@ import { LoadingOutlined } from '@ant-design/icons'
|
||||
import EmptySVG from '@common/assets/empty.svg'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import ApiDocument from '@common/components/aoplatform/ApiDocument.tsx'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import {
|
||||
AiServiceInsideApiDocumentHandle,
|
||||
AiServiceInsideApiDocumentProps,
|
||||
AiServiceApiDetail
|
||||
} from '@core/const/ai-service/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
|
||||
|
||||
const AiServiceInsideApiDocument = forwardRef<AiServiceInsideApiDocumentHandle, AiServiceInsideApiDocumentProps>(() => {
|
||||
const { serviceId, teamId } = useParams<RouterParams>()
|
||||
const { fetchData } = useFetch()
|
||||
const [apiDetail, setApiDetail] = useState<AiServiceApiDetail>()
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const navigator = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('API 文档')
|
||||
}
|
||||
])
|
||||
getApiDetail()
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -291,19 +291,6 @@ const AiServiceInsideRouterCreate = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('API'),
|
||||
onClick: () => navigator(backUrl)
|
||||
},
|
||||
{
|
||||
title: routeId ? $t('编辑 API') : $t('添加 API')
|
||||
}
|
||||
])
|
||||
!routeId && aiServiceInfo?.provider && getDefaultModelConfig()
|
||||
}, [aiServiceInfo])
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import PageList, { PageProColumns } from '@common/components/aoplatform/PageList
|
||||
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx'
|
||||
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { SimpleMemberItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
@@ -17,7 +16,6 @@ import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
const AiServiceInsideRouterList: FC = () => {
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal, message } = App.useApp()
|
||||
const [tableListDataSource, setTableListDataSource] = useState<AiServiceRouterTableListItem[]>([])
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true)
|
||||
@@ -162,17 +160,6 @@ const AiServiceInsideRouterList: FC = () => {
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId])
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('路由')
|
||||
}
|
||||
])
|
||||
}, [state.language])
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return [...AI_SERVICE_ROUTER_TABLE_COLUMNS].map((x) => {
|
||||
|
||||
@@ -3,7 +3,6 @@ import {ActionType} from "@ant-design/pro-components";
|
||||
import {FC, useEffect, useMemo, useRef, useState} from "react";
|
||||
import {Link, useLocation, useNavigate, useParams} from "react-router-dom";
|
||||
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx";
|
||||
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {App, Button} from "antd";
|
||||
import {
|
||||
SUBSCRIBE_APPROVAL_INNER_DONE_TABLE_COLUMN,
|
||||
@@ -26,7 +25,6 @@ import { SubscribeApprovalInfoType } from "@common/const/approval/type.tsx";
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
const AiServiceInsideApprovalList:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal,message } = App.useApp()
|
||||
const {serviceId, teamId} = useParams<RouterParams>();
|
||||
const [init, setInit] = useState<boolean>(true)
|
||||
@@ -40,7 +38,6 @@ const AiServiceInsideApprovalList:FC = ()=>{
|
||||
const [approvalBtnLoading,setApprovalBtnLoading] = useState<boolean>(false)
|
||||
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
|
||||
const {accessData,state} = useGlobalContext()
|
||||
const navigator = useNavigate()
|
||||
|
||||
const openModal = async (type:'approval'|'view',entity:SubscribeApprovalTableListItem)=>{
|
||||
message.loading($t(RESPONSE_TIPS.loading))
|
||||
@@ -142,15 +139,6 @@ const AiServiceInsideApprovalList:FC = ()=>{
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('订阅审核')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
import { Tabs } from "antd"
|
||||
import { useState, useEffect, FC, useMemo } from "react"
|
||||
import { Link, Outlet, useLocation, useNavigate } from "react-router-dom"
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"
|
||||
import { SYSTEM_PUBLISH_TAB_ITEMS } from "../../../const/system/const"
|
||||
import { $t } from "@common/locales"
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
|
||||
|
||||
const AiServiceInsidePublic:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const query =new URLSearchParams(useLocation().search)
|
||||
const location = useLocation()
|
||||
const currentUrl = location.pathname
|
||||
@@ -25,18 +23,6 @@ const AiServiceInsidePublic:FC = ()=>{
|
||||
setPageStatus(Number(query.get('status') ||0) as 0|1)
|
||||
}, [currentUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigateTo('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('发布')
|
||||
}
|
||||
])
|
||||
}, []);
|
||||
|
||||
const tabItems = useMemo(()=>SYSTEM_PUBLISH_TAB_ITEMS?.map((x)=>({...x, label:$t(x.label as string) })),[state.language])
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -9,7 +9,6 @@ import { PUBLISH_APPROVAL_RECORD_INNER_TABLE_COLUMN, PUBLISH_APPROVAL_VERSION_IN
|
||||
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
|
||||
import { SimpleMemberItem } from "@common/const/type.ts";
|
||||
import { MemberTableListItem } from "../../../const/member/type";
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext";
|
||||
import { useFetch } from "@common/hooks/http";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission";
|
||||
import { AiServicePublishReleaseItem } from "../../../const/system/type";
|
||||
@@ -23,7 +22,6 @@ import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
const AiServiceInsidePublicList:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal,message } = App.useApp()
|
||||
const pageListRef = useRef<ActionType>(null);
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true);
|
||||
@@ -45,7 +43,6 @@ const AiServiceInsidePublicList:FC = ()=>{
|
||||
const [drawerData, setDrawerData] = useState<PublishTableListItem|PublishVersionTableListItem >({} as PublishTableListItem)
|
||||
const [drawerOkTitle, setDrawerOkTitle] = useState<string>('确认')
|
||||
const [isOkToPublish, setIsOkToPublish] = useState<boolean>(false)
|
||||
const navigator = useNavigate()
|
||||
const getAiServicePublishList = (params?: ParamsType & {
|
||||
pageSize?: number | undefined;
|
||||
current?: number | undefined;
|
||||
@@ -351,15 +348,6 @@ const AiServiceInsidePublicList:FC = ()=>{
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('发布')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
@@ -68,7 +68,7 @@ const LogSettings = () => {
|
||||
<>
|
||||
<Skeleton className="m-btnbase w-calc-100vw-minus-padding-r" active loading={loading}>
|
||||
<InsidePage
|
||||
pageTitle={$t('日志配置')}
|
||||
pageTitle={$t('日志输出设置')}
|
||||
description={'APIPark ' + $t('提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。')}
|
||||
>
|
||||
<div className="flex h-full">
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import EditableTable from "@common/components/aoplatform/EditableTable"
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission"
|
||||
import { BasicResponse, PLACEHOLDER, STATUS_CODE } from "@common/const/const"
|
||||
import { useFetch } from "@common/hooks/http"
|
||||
import { $t } from "@common/locales"
|
||||
import { PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS, PartitionDataLogConfigFieldType, PartitionDataLogHeaderListFieldType } from "@core/const/partitions/types"
|
||||
import { Button, Form, Input, message } from "antd"
|
||||
import { useEffect } from "react"
|
||||
import EditableTable from '@common/components/aoplatform/EditableTable'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { BasicResponse, PLACEHOLDER, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { $t } from '@common/locales'
|
||||
import {
|
||||
PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS,
|
||||
PartitionDataLogConfigFieldType,
|
||||
PartitionDataLogHeaderListFieldType
|
||||
} from '@core/const/partitions/types'
|
||||
import { Button, Form, Input, message, Select } from 'antd'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export type DashboardPageShowStatus = 'view' | 'edit'
|
||||
export type DashboardSettingEditProps = {
|
||||
@@ -15,7 +19,7 @@ export type DashboardSettingEditProps = {
|
||||
}
|
||||
const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
|
||||
const { changeStatus, refreshData, data } = props
|
||||
const [form] = Form.useForm();
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
const onFinish = () => {
|
||||
@@ -23,10 +27,16 @@ const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
|
||||
const formData = {
|
||||
config: {
|
||||
url: value.url,
|
||||
headers: value.headers.filter((item: PartitionDataLogHeaderListFieldType) => item.key).map((item: PartitionDataLogHeaderListFieldType) => ({key:item.key, value:item.value || ''}))
|
||||
headers: value.headers
|
||||
.filter((item: PartitionDataLogHeaderListFieldType) => item.key)
|
||||
.map((item: PartitionDataLogHeaderListFieldType) => ({ key: item.key, value: item.value || '' }))
|
||||
}
|
||||
}
|
||||
fetchData<BasicResponse<{ info: PartitionDataLogConfigFieldType }>>('log/loki', { method: 'POST', body: JSON.stringify(formData), eoParams: {} }).then(response => {
|
||||
fetchData<BasicResponse<{ info: PartitionDataLogConfigFieldType }>>('log/loki', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(formData),
|
||||
eoParams: {}
|
||||
}).then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t('操作成功,即将刷新页面'))
|
||||
@@ -38,15 +48,26 @@ const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => { form.setFieldsValue(data) }, [data])
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
...data,
|
||||
headers: data?.headers?.length ? data.headers : [
|
||||
{
|
||||
key: '',
|
||||
value: ''
|
||||
}
|
||||
],
|
||||
driver: 'loki'
|
||||
})
|
||||
}, [data])
|
||||
|
||||
useEffect(() => {
|
||||
return (form.setFieldsValue({}))
|
||||
}, []);
|
||||
return form.setFieldsValue({})
|
||||
}, [])
|
||||
return (
|
||||
<>
|
||||
<div className="overflow-auto h-full">
|
||||
<WithPermission access={''} >
|
||||
<WithPermission access={''}>
|
||||
<Form
|
||||
form={form}
|
||||
className="mx-auto flex flex-col justify-between h-full"
|
||||
@@ -55,23 +76,29 @@ const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item<PartitionDataLogConfigFieldType>
|
||||
label={$t("请求前缀")}
|
||||
name="url"
|
||||
label={$t('数据源类型')}
|
||||
name="driver"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={[{ label: 'Loki', value: 'loki' }]}
|
||||
></Select>
|
||||
</Form.Item>
|
||||
<Form.Item<PartitionDataLogConfigFieldType> label={$t('请求前缀')} name="url" rules={[{ required: true }]}>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<PartitionDataLogConfigFieldType>
|
||||
label={$t("HTTP 头部")}
|
||||
name="headers"
|
||||
>
|
||||
<Form.Item<PartitionDataLogConfigFieldType> label={$t('HTTP 头部')} name="headers">
|
||||
<EditableTable<PartitionDataLogConfigFieldType & { _id: string }>
|
||||
configFields={PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="flex gap-btnbase">
|
||||
<WithPermission access='system.devops.data_source.edit'>
|
||||
<WithPermission access="system.devops.data_source.edit">
|
||||
<Button type="primary" htmlType="submit">
|
||||
{$t('保存')}
|
||||
</Button>
|
||||
@@ -84,7 +111,7 @@ const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
|
||||
</WithPermission>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default DataLogSettingEdit;
|
||||
export default DataLogSettingEdit
|
||||
|
||||
@@ -161,7 +161,7 @@ const PartitionInsideDashboardSetting: FC = () => {
|
||||
className="overflow-hidden mt-[30px] w-full max-h-full flex flex-col justify-between"
|
||||
title={
|
||||
<div>
|
||||
<span className="text-MAIN_TEXT my-btnybase mr-btnbase"> {$t('数据日志')}</span>
|
||||
<span className="text-MAIN_TEXT my-btnybase mr-btnbase"> {$t('请求日志')}</span>
|
||||
{!dataLogLoading && !dataLogData && <Tag color="#f50">{$t('未配置')}</Tag>}
|
||||
</div>
|
||||
}
|
||||
@@ -220,6 +220,11 @@ export function DataLogConfigPreview(x: PartitionDataLogConfigFieldType) {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-[4px] ">
|
||||
<Row className="">
|
||||
<Col className="font-bold text-right pr-[4px]">{$t('数据源')}:</Col>
|
||||
{/* 先写死,或许会有选择列表,但现在可以不用 */}
|
||||
<Col>Loki</Col>
|
||||
</Row>
|
||||
<Row className="">
|
||||
<Col className="font-bold text-right pr-[4px]">{$t('请求前缀')}:</Col>
|
||||
<Col>{x?.url}</Col>
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
|
||||
import { useEffect } from 'react'
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
export default function ServicePolicyLayout() {
|
||||
const location = useLocation()
|
||||
const pathName = location.pathname
|
||||
const navigator = useNavigate()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
useEffect(() => {
|
||||
const tmpPath = pathName.split('/')
|
||||
if (tmpPath[tmpPath.length - 1] === 'servicepolicy') {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('服务策略')
|
||||
}
|
||||
])
|
||||
navigator('datamasking/list')
|
||||
}
|
||||
}, [pathName])
|
||||
|
||||
@@ -80,19 +80,6 @@ const DataMaskingConfig = forwardRef<DataMaskingConfigHandle>((_,ref) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('服务策略'),
|
||||
onClick: () => navigator(serviceId ? `/service/${teamId}/aiInside/${serviceId}/servicepolicy` : '')
|
||||
},
|
||||
{
|
||||
title: policyId !== undefined ? $t('编辑服务策略') : $t('添加服务策略')
|
||||
}
|
||||
])
|
||||
if (policyId !== undefined) {
|
||||
setOnEdit(true);
|
||||
getPolicyInfo();
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import ServiceLogs from "./ServiceLogs"
|
||||
const AiServiceLogsContainer = () => {
|
||||
return <ServiceLogs serviceType="aiService" />
|
||||
}
|
||||
|
||||
export default AiServiceLogsContainer
|
||||
@@ -0,0 +1,89 @@
|
||||
import { IconButton } from '@common/components/postcat/api/IconButton'
|
||||
import useCopyToClipboard from '@common/hooks/copy'
|
||||
import { RESPONSE_TIPS } from '@common/const/const'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { App } from 'antd'
|
||||
import ReactJson from 'react-json-view'
|
||||
|
||||
const ApiNetWorkDataPreview = ({ configContent = {} }: { configContent?: { [key: string]: string | undefined } }) => {
|
||||
/** 复制组件 */
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
/** 弹窗组件 */
|
||||
const { message } = App.useApp()
|
||||
/**
|
||||
* 复制
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
const handleCopy = async (value: string): Promise<void> => {
|
||||
if (value) {
|
||||
copyToClipboard(value)
|
||||
message.success($t(RESPONSE_TIPS.copySuccess))
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 判断字符串是否是有效的JSON对象字符串
|
||||
*/
|
||||
const isJsonString = (str: string): boolean => {
|
||||
try {
|
||||
const parsed = JSON.parse(str)
|
||||
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.keys(configContent).map((item) => {
|
||||
return (
|
||||
<div className="overflow-auto">
|
||||
<div className="font-semibold text-[16px] mb-[10px]">{item}</div>
|
||||
<div className="bg-[#0a0b21] text-white p-4 rounded-md my-2 font-mono text-sm overflow-auto relative">
|
||||
{!configContent[item] ? (
|
||||
<pre className="whitespace-pre-wrap break-words"></pre>
|
||||
) : isJsonString(configContent[item] || '') ? (
|
||||
// 如果是有效的JSON对象字符串,使用ReactJson渲染
|
||||
<ReactJson
|
||||
src={JSON.parse(configContent[item] || '')}
|
||||
theme="monokai"
|
||||
indentWidth={2}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
name={false}
|
||||
collapsed={false}
|
||||
enableClipboard={false}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'normal'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
// 如果是普通字符串,直接用pre渲染
|
||||
<pre className="whitespace-pre-wrap break-words my-[8px]">{configContent[item]}</pre>
|
||||
)}
|
||||
<IconButton
|
||||
name="copy"
|
||||
onClick={() => handleCopy(configContent[item] || '')}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '5px',
|
||||
right: '5px',
|
||||
color: '#999',
|
||||
transition: 'none',
|
||||
'&.MuiButtonBase-root:hover': {
|
||||
background: 'transparent',
|
||||
color: '#3D46F2',
|
||||
transition: 'none'
|
||||
}
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default ApiNetWorkDataPreview
|
||||
@@ -0,0 +1,362 @@
|
||||
import { Descriptions, DescriptionsProps, Spin, Tabs, Tooltip, message } from 'antd'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import React from 'react'
|
||||
import { ExclamationCircleOutlined, LoadingOutlined } from '@ant-design/icons'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import ApiNetWorkDataPreview from './ApiNetWorkDataPreview'
|
||||
import { LogItem } from './ServiceLogs'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
|
||||
// 定义状态码颜色映射枚举
|
||||
export enum HttpStatusColor {
|
||||
SUCCESS = '#7EC26A',
|
||||
CLIENT_ERROR = '#F2CF59',
|
||||
SERVER_ERROR = '#f80f34'
|
||||
}
|
||||
|
||||
type LogDetailProps = {
|
||||
selectedRow?: LogItem
|
||||
serviceType: 'aiService' | 'restService'
|
||||
serviceId?: string
|
||||
teamId?: string
|
||||
}
|
||||
|
||||
type AIServiceDetailType = {
|
||||
id: string
|
||||
api: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
logTime: string
|
||||
consumer: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
isSystemConsumer: boolean
|
||||
status: string
|
||||
provider: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
model: string
|
||||
ip: string
|
||||
request: {
|
||||
header: string
|
||||
body: string
|
||||
origin: string
|
||||
token: number
|
||||
}
|
||||
response: {
|
||||
header: string
|
||||
body: string
|
||||
origin: string
|
||||
token: string
|
||||
}
|
||||
}
|
||||
|
||||
type RestServiceDetailType = {
|
||||
id: string
|
||||
api: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
logTime: string
|
||||
consumer: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
isSystemConsumer: boolean
|
||||
status: string
|
||||
ip: string
|
||||
request: {
|
||||
header: string
|
||||
origin: string
|
||||
}
|
||||
response: {
|
||||
header: string
|
||||
origin: string
|
||||
}
|
||||
}
|
||||
|
||||
const LogDetail = ({ selectedRow, serviceType, serviceId, teamId }: LogDetailProps) => {
|
||||
/** 顶部描述 */
|
||||
const [descriptionItems, setDescriptionItems] = useState<DescriptionsProps['items']>()
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
/** Request 标签页数据 */
|
||||
const [requestInfoData, setRequestInfoData] = useState<{ [key: string]: string | undefined }>()
|
||||
/** Response 标签页数据 */
|
||||
const [responseInfoData, setResponseInfoData] = useState<{ [key: string]: string | undefined }>()
|
||||
/** 面板 loading */
|
||||
const [dashboardLoading, setDashboardLoading] = useState(true)
|
||||
/**
|
||||
* 请求数据
|
||||
*/
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
/**
|
||||
* 根据状态码返回对应颜色的文本
|
||||
* @param status 状态
|
||||
* @returns
|
||||
*/
|
||||
const renderStatusWithColor = (status: string) => {
|
||||
// 获取状态码首位数字
|
||||
const firstDigit = String(status).charAt(0)
|
||||
let color = ''
|
||||
switch (firstDigit) {
|
||||
case '2':
|
||||
color = HttpStatusColor.SUCCESS
|
||||
break
|
||||
case '4':
|
||||
color = HttpStatusColor.CLIENT_ERROR
|
||||
break
|
||||
case '5':
|
||||
color = HttpStatusColor.SERVER_ERROR
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return color ? <span style={{ color }}>{status}</span> : status
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签页内容
|
||||
*/
|
||||
const tabItems = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: 'request',
|
||||
label: 'Request',
|
||||
children: <ApiNetWorkDataPreview configContent={requestInfoData} />
|
||||
},
|
||||
{
|
||||
key: 'response',
|
||||
label: 'Response',
|
||||
children: <ApiNetWorkDataPreview configContent={responseInfoData} />
|
||||
}
|
||||
],
|
||||
[state.language, requestInfoData, responseInfoData]
|
||||
)
|
||||
|
||||
/**
|
||||
* 设置 AI 描述文案
|
||||
*/
|
||||
const getAIServiceDescriptionItemsList = ({
|
||||
time,
|
||||
api,
|
||||
consumer,
|
||||
status,
|
||||
model,
|
||||
ip
|
||||
}: {
|
||||
time: string
|
||||
api: string
|
||||
consumer: string
|
||||
status: string
|
||||
model: string
|
||||
ip: string
|
||||
}) => {
|
||||
setDescriptionItems([
|
||||
{
|
||||
key: 'time',
|
||||
label: $t('时间'),
|
||||
children: time
|
||||
},
|
||||
{
|
||||
key: 'api',
|
||||
label: $t('API / Tools'),
|
||||
children: api
|
||||
},
|
||||
{
|
||||
key: 'consumer',
|
||||
label: $t('消费者'),
|
||||
children: consumer
|
||||
},
|
||||
{
|
||||
key: 'httpStatus',
|
||||
label: $t('HTTP 状态'),
|
||||
children: renderStatusWithColor(status)
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
label: $t('模型'),
|
||||
children: model
|
||||
},
|
||||
{
|
||||
key: 'ip',
|
||||
label: $t('IP'),
|
||||
children: ip
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 REST 描述文案
|
||||
*/
|
||||
const getRestServiceDescriptionItemsList = ({
|
||||
time,
|
||||
api,
|
||||
consumer,
|
||||
isSystemConsumer,
|
||||
status,
|
||||
ip
|
||||
}: {
|
||||
time: string
|
||||
api: string
|
||||
consumer: string
|
||||
isSystemConsumer?: boolean
|
||||
status: string
|
||||
ip: string
|
||||
}) => {
|
||||
setDescriptionItems([
|
||||
{
|
||||
key: 'time',
|
||||
label: $t('时间'),
|
||||
children: time
|
||||
},
|
||||
{
|
||||
key: 'api',
|
||||
label: $t('API / Tools'),
|
||||
children: api
|
||||
},
|
||||
{
|
||||
key: 'consumer',
|
||||
label: $t('消费者'),
|
||||
children: (
|
||||
<>
|
||||
<span className="mr-[50px]">{consumer}</span>
|
||||
{isSystemConsumer && (
|
||||
<span>
|
||||
<span>System-level API Key</span>
|
||||
<Tooltip title={$t('通过系统级别的 API Key 来调用')}>
|
||||
<span className="ml-[12px] items-center">
|
||||
<ExclamationCircleOutlined className="text-[14px] h-[14px] w-[14px]" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'httpStatus',
|
||||
label: $t('HTTP 状态'),
|
||||
children: renderStatusWithColor(status)
|
||||
},
|
||||
{
|
||||
key: 'ip',
|
||||
label: $t('IP'),
|
||||
children: ip
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AI 服务日志详情
|
||||
*/
|
||||
const getAIServiceLogDetail = () => {
|
||||
fetchData<BasicResponse<{ log: AIServiceDetailType }>>('service/log/ai', {
|
||||
method: 'GET',
|
||||
eoParams: { log: selectedRow?.id, service: serviceId, team: teamId },
|
||||
eoTransformKeys: ['is_system_consumer', 'log_time']
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const result = data.log
|
||||
getAIServiceDescriptionItemsList({
|
||||
time: result.logTime,
|
||||
api: result.api.name,
|
||||
consumer: result.consumer.name,
|
||||
status: result.status,
|
||||
model: result.model,
|
||||
ip: result.ip
|
||||
})
|
||||
setRequestInfoData({
|
||||
Header: result.request.header,
|
||||
Body: result.request.body
|
||||
})
|
||||
setResponseInfoData({
|
||||
Header: result.response.header,
|
||||
Body: result.response.body
|
||||
})
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
setDashboardLoading(false)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 获取 REST 服务日志详情
|
||||
*/
|
||||
const getRestServiceLogDetail = () => {
|
||||
fetchData<BasicResponse<{ log: RestServiceDetailType }>>('service/log/rest', {
|
||||
method: 'GET',
|
||||
eoParams: { log: selectedRow?.id, service: serviceId, team: teamId },
|
||||
eoTransformKeys: ['is_system_consumer', 'log_time']
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const result = data.log
|
||||
getRestServiceDescriptionItemsList({
|
||||
time: result.logTime,
|
||||
api: result.api.name,
|
||||
consumer: result.consumer.name,
|
||||
status: result.status,
|
||||
ip: result.ip,
|
||||
isSystemConsumer: result.isSystemConsumer
|
||||
})
|
||||
setRequestInfoData({
|
||||
Header: result.request.header,
|
||||
Body: result.request.body
|
||||
})
|
||||
setResponseInfoData({
|
||||
Header: result.response.header,
|
||||
Body: result.response.body
|
||||
})
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
setDashboardLoading(false)
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
setDashboardLoading(true)
|
||||
serviceType === 'aiService' ? getAIServiceLogDetail() : getRestServiceLogDetail()
|
||||
}, [serviceType])
|
||||
|
||||
return (
|
||||
<Spin
|
||||
className="h-full pb-[20px]"
|
||||
wrapperClassName="h-full min-h-[150px]"
|
||||
indicator={
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ transform: 'scale(1.5)' }}>
|
||||
<LoadingOutlined style={{ fontSize: 30 }} spin />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
spinning={dashboardLoading}
|
||||
>
|
||||
<Descriptions
|
||||
column={1}
|
||||
className="[&_.ant-descriptions-item]:p-0 [&_.ant-descriptions-item]:py-[5px]"
|
||||
colon={false}
|
||||
items={descriptionItems}
|
||||
classNames={{
|
||||
label: 'w-[250px] text-right pr-[12px]'
|
||||
}}
|
||||
contentStyle={{ fontWeight: '600' }}
|
||||
/>
|
||||
<div className="mt-[5px]">
|
||||
<Tabs
|
||||
className="overflow-hidden h-full [&>.ant-tabs-content-holder]:overflow-auto global-policy-tabs"
|
||||
items={tabItems}
|
||||
/>
|
||||
</div>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
|
||||
export default LogDetail
|
||||
@@ -0,0 +1,7 @@
|
||||
import ServiceLogs from "./ServiceLogs"
|
||||
|
||||
const RestServiceLogsContainer = () => {
|
||||
return <ServiceLogs serviceType="restService" />
|
||||
}
|
||||
|
||||
export default RestServiceLogsContainer
|
||||
@@ -0,0 +1,264 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { Drawer, Spin, message } from 'antd'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import DateSelectFilter, { TimeOption } from '../serviceOverview/filter/DateSelectFilter'
|
||||
import { TimeRange } from '@common/components/aoplatform/TimeRangeSelector'
|
||||
import PageList from '@common/components/aoplatform/PageList'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { REST_SERVICE_LOG_LIST, AI_SERVICE_LOG_LIST } from '@core/const/system/const'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import LogDetail, { HttpStatusColor } from './LogDetail'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { ActionType, ParamsType } from '@ant-design/pro-components'
|
||||
import { getTime } from '@dashboard/utils/dashboard'
|
||||
|
||||
export type LogItem = {
|
||||
id: string
|
||||
api: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
status: number
|
||||
logTime: string
|
||||
responseTime: string
|
||||
token?: number
|
||||
model?: string
|
||||
tokenPerSecond?: string
|
||||
traffic?: string
|
||||
consumers?: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
provider?: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
const ServiceLogs = ({ serviceType }: { serviceType: 'aiService' | 'restService' }) => {
|
||||
/** 路由参数 */
|
||||
const { serviceId, teamId } = useParams<{ serviceId: string; teamId: string }>()
|
||||
/** 面板 loading */
|
||||
const [dashboardLoading, setDashboardLoading] = useState(true)
|
||||
/** 当前选中的时间范围 */
|
||||
const [timeRange, setTimeRange] = useState<TimeRange | undefined>()
|
||||
/** 默认时间 */
|
||||
const [defaultTime] = useState<TimeOption>('sevenDays')
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
/**
|
||||
* 请求数据
|
||||
*/
|
||||
const { fetchData } = useFetch()
|
||||
// 打开侧边弹窗
|
||||
const [drawerOpen, setDrawerOpen] = useState<boolean>(false)
|
||||
/** 选中的行 */
|
||||
const [selectedRow, setSelectedRow] = useState<LogItem>()
|
||||
/**
|
||||
* 列表ref
|
||||
*/
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
/** 列 */
|
||||
const columns = useMemo(() => {
|
||||
return [...(serviceType === 'aiService' ? AI_SERVICE_LOG_LIST : REST_SERVICE_LOG_LIST)].map((x) => {
|
||||
if (x.dataIndex === 'status') {
|
||||
x.render = (text: any, record: any) => (
|
||||
<>
|
||||
<div className="w-full">{renderStatusWithColor(record.status)}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return {
|
||||
...x,
|
||||
title: typeof x.title === 'string' ? $t(x.title as string) : x.title
|
||||
}
|
||||
})
|
||||
}, [state.language])
|
||||
|
||||
/**
|
||||
* 根据状态码返回对应颜色的文本
|
||||
* @param status 状态
|
||||
* @returns
|
||||
*/
|
||||
const renderStatusWithColor = (status: string | number) => {
|
||||
// 获取状态码首位数字
|
||||
const firstDigit = status.toString().charAt(0)
|
||||
let color = ''
|
||||
switch (firstDigit) {
|
||||
case '2':
|
||||
color = HttpStatusColor.SUCCESS
|
||||
break
|
||||
case '4':
|
||||
color = HttpStatusColor.CLIENT_ERROR
|
||||
break
|
||||
case '5':
|
||||
color = HttpStatusColor.SERVER_ERROR
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return color ? <span style={{ color }}>{status}</span> : status
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AI 列表数据
|
||||
* @param dataType
|
||||
* @returns
|
||||
*/
|
||||
const getAiServiceLogList = (
|
||||
params: ParamsType & {
|
||||
pageSize?: number | undefined
|
||||
current?: number | undefined
|
||||
keyword?: string | undefined
|
||||
}
|
||||
) => {
|
||||
return fetchData<BasicResponse<{ log: LogItem[] }>>(`service/logs/ai`, {
|
||||
method: 'GET',
|
||||
eoParams: {
|
||||
service: serviceId,
|
||||
team: teamId,
|
||||
start: timeRange?.start,
|
||||
end: timeRange?.end,
|
||||
page: params?.current,
|
||||
page_size:params?.pageSize
|
||||
},
|
||||
eoTransformKeys: ['log_time', 'response_time', 'token_per_second']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 保存数据
|
||||
return {
|
||||
data: data.logs,
|
||||
total: data.total,
|
||||
success: true
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return { data: [], success: false }
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
return { data: [], success: false }
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 获取 REST 列表数据
|
||||
* @param dataType
|
||||
* @returns
|
||||
*/
|
||||
const getRestServiceLogList = (
|
||||
params: ParamsType & {
|
||||
pageSize?: number | undefined
|
||||
current?: number | undefined
|
||||
keyword?: string | undefined
|
||||
}
|
||||
) => {
|
||||
console.log('params===', params)
|
||||
return fetchData<BasicResponse<{ log: LogItem[] }>>(`service/logs/rest`, {
|
||||
method: 'GET',
|
||||
eoParams: {
|
||||
service: serviceId,
|
||||
team: teamId,
|
||||
start: timeRange?.start,
|
||||
end: timeRange?.end,
|
||||
page: params?.current,
|
||||
page_size:params?.pageSize
|
||||
},
|
||||
eoTransformKeys: ['log_time', 'response_time', 'token_per_second']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 保存数据
|
||||
return {
|
||||
data: data.logs,
|
||||
total: data.total,
|
||||
success: true
|
||||
}
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return { data: [], success: false }
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
return { data: [], success: false }
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const { startTime, endTime } = getTime(defaultTime, [])
|
||||
setTimeRange({
|
||||
start: startTime,
|
||||
end: endTime
|
||||
})
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
if (timeRange) {
|
||||
pageListRef.current?.reload()
|
||||
}
|
||||
}, [timeRange])
|
||||
|
||||
/** 行点击 */
|
||||
const handleRowClick = (record: LogItem) => {
|
||||
setSelectedRow(record)
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
|
||||
/** 时间选择回调 */
|
||||
const selectCallback = (date: TimeRange) => {
|
||||
setTimeRange(date)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setDashboardLoading(false)
|
||||
}, [])
|
||||
return (
|
||||
<Spin
|
||||
className="h-full pb-[20px]"
|
||||
wrapperClassName="h-full min-h-[150px]"
|
||||
indicator={
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ transform: 'scale(1.5)' }}>
|
||||
<LoadingOutlined style={{ fontSize: 30 }} spin />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
spinning={dashboardLoading}
|
||||
>
|
||||
<div className="mr-PAGE_INSIDE_X">
|
||||
<DateSelectFilter selectCallback={selectCallback} customClassNames={'pt-[0px]'} defaultTime={defaultTime} />
|
||||
<div className="mt-[20px]">
|
||||
<PageList
|
||||
ref={pageListRef}
|
||||
id={`${serviceType}_logs`}
|
||||
columns={[...columns]}
|
||||
minVirtualHeight={430}
|
||||
request={async (
|
||||
params: ParamsType & {
|
||||
pageSize?: number | undefined
|
||||
current?: number | undefined
|
||||
keyword?: string | undefined
|
||||
}
|
||||
) => (serviceType === 'aiService' ? getAiServiceLogList(params) : getRestServiceLogList(params))}
|
||||
onRowClick={(row: LogItem) => handleRowClick(row)}
|
||||
/>
|
||||
</div>
|
||||
<Drawer
|
||||
destroyOnClose={true}
|
||||
maskClosable={false}
|
||||
title={$t('日志详情')}
|
||||
width={'40%'}
|
||||
onClose={() => setDrawerOpen(false)}
|
||||
open={drawerOpen}
|
||||
>
|
||||
<LogDetail selectedRow={selectedRow} serviceId={serviceId} teamId={teamId} serviceType={serviceType} />
|
||||
</Drawer>
|
||||
</div>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceLogs
|
||||
@@ -0,0 +1,7 @@
|
||||
import ServiceOverview from "./serviceOverview"
|
||||
|
||||
const AiServiceContainer = () => {
|
||||
return <ServiceOverview serviceType="aiService" />
|
||||
}
|
||||
|
||||
export default AiServiceContainer
|
||||
@@ -0,0 +1,12 @@
|
||||
import { FC } from 'react'
|
||||
import ServiceOverview from './serviceOverview'
|
||||
|
||||
const RestServiceContainer: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<ServiceOverview serviceType="restService" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default RestServiceContainer
|
||||
@@ -0,0 +1,236 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import ECharts, { EChartsOption } from 'echarts-for-react'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
type AreaChartInfo = {
|
||||
title: string
|
||||
value: string
|
||||
date: string[]
|
||||
data: number[]
|
||||
max: string
|
||||
min: string
|
||||
showXAxis?: boolean
|
||||
}
|
||||
|
||||
type ServiceAreaCharProps = {
|
||||
customClassNames?: string
|
||||
dataInfo?: AreaChartInfo
|
||||
height?: number
|
||||
}
|
||||
|
||||
const ServiceAreaChart = ({ customClassNames, dataInfo, height }: ServiceAreaCharProps) => {
|
||||
const chartRef = useRef<ECharts>(null)
|
||||
const [option, setOption] = useState<EChartsOption | undefined>({})
|
||||
const setChartOption = (dataInfo: AreaChartInfo) => {
|
||||
const hasData = dataInfo.data && dataInfo.data.length > 0
|
||||
const option = {
|
||||
tooltip: hasData ? {
|
||||
trigger: 'axis',
|
||||
formatter: function (value: any) {
|
||||
// 如果是数组,取第一个参数的name
|
||||
const param = Array.isArray(value) ? value[0] : value
|
||||
let tooltipContent = `<div style="min-width:140px;padding:8px;">`
|
||||
const marker = `<span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${param.color};"></span>`
|
||||
tooltipContent += `<div style="margin-top: 8px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="margin-right: 4px;">${marker}</div>${param.name} <div style="font-weight:bold; margin-left: 20px;">${param.value}</div>
|
||||
</div>`
|
||||
tooltipContent += '</div>'
|
||||
return tooltipContent
|
||||
}
|
||||
} : {
|
||||
show: false // 没有数据时不显示tooltip
|
||||
},
|
||||
title: [
|
||||
{
|
||||
text: '{titleStyle|' + $t(dataInfo.title) + '}\n\n{valueStyle|' + dataInfo.value + '}',
|
||||
left: '2%',
|
||||
top: '0',
|
||||
textStyle: {
|
||||
rich: {
|
||||
titleStyle: {
|
||||
fontSize: 14,
|
||||
color: '#999',
|
||||
fontWeight: 'normal',
|
||||
lineHeight: 20
|
||||
},
|
||||
valueStyle: {
|
||||
fontSize: 32,
|
||||
color: '#101010',
|
||||
fontWeight: 500,
|
||||
lineHeight: 40
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
toolbox: {
|
||||
show: false
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '3%',
|
||||
bottom: '0%',
|
||||
top: '110px',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: dataInfo.date,
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#ccc'
|
||||
}
|
||||
},
|
||||
show: false
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
boundaryGap: [0, '5%'],
|
||||
show: hasData, // 没有数据时不显示Y轴
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
// 添加数据缩放组件,实现鼠标放大缩小,后续可能需要
|
||||
// dataZoom: [
|
||||
// {
|
||||
// type: 'inside', // 内置的数据区域缩放组件(使用鼠标滚轮缩放)
|
||||
// xAxisIndex: 0, // 设置缩放作用在第一个x轴
|
||||
// filterMode: 'filter',
|
||||
// start: 0,
|
||||
// end: 100
|
||||
// },
|
||||
// {
|
||||
// type: 'slider', // 滑动条型数据区域缩放组件
|
||||
// xAxisIndex: 0,
|
||||
// filterMode: 'filter',
|
||||
// height: 20,
|
||||
// bottom: 0,
|
||||
// start: 0,
|
||||
// end: 100,
|
||||
// handleIcon:
|
||||
// 'path://M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
|
||||
// handleSize: '80%',
|
||||
// handleStyle: {
|
||||
// color: '#fff',
|
||||
// shadowBlur: 3,
|
||||
// shadowColor: 'rgba(0, 0, 0, 0.6)',
|
||||
// shadowOffsetX: 2,
|
||||
// shadowOffsetY: 2
|
||||
// },
|
||||
// show: false // 默认隐藏底部的滑动条,可以改为 true 显示
|
||||
// }
|
||||
// ],
|
||||
// 添加空状态提示
|
||||
graphic: !hasData
|
||||
? [
|
||||
{
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'middle',
|
||||
style: {
|
||||
text: $t('暂无数据'),
|
||||
fontSize: 14,
|
||||
fill: '#999'
|
||||
}
|
||||
}
|
||||
]
|
||||
: [],
|
||||
series: [
|
||||
{
|
||||
name: dataInfo.title,
|
||||
type: 'line',
|
||||
symbol: 'none',
|
||||
sampling: 'lttb',
|
||||
itemStyle: {
|
||||
color: 'rgb(255, 70, 131)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgb(255, 158, 68)'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgb(255, 70, 131)'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
data: dataInfo.data
|
||||
}
|
||||
]
|
||||
}
|
||||
setOption(option)
|
||||
}
|
||||
// 使用深度监听来确保图表数据更新
|
||||
useEffect(() => {
|
||||
if (!dataInfo) return
|
||||
|
||||
// 直接获取 ECharts 实例并设置选项
|
||||
const echartsInstance = chartRef.current?.getEchartsInstance()
|
||||
if (echartsInstance) {
|
||||
// 清除已有的图表
|
||||
echartsInstance.clear()
|
||||
// 重新设置选项
|
||||
setChartOption(dataInfo)
|
||||
}
|
||||
}, [dataInfo, JSON.stringify(dataInfo)])
|
||||
|
||||
// 添加窗口大小变化监听,实现自适应
|
||||
useEffect(() => {
|
||||
// 定义resize处理函数
|
||||
const handleResize = () => {
|
||||
const echartsInstance = chartRef.current?.getEchartsInstance()
|
||||
if (echartsInstance) {
|
||||
echartsInstance.resize()
|
||||
}
|
||||
}
|
||||
|
||||
// 添加监听
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
// 组件卸载时移除监听
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<div className={`w-full ${customClassNames}`}>
|
||||
<div className="absolute top-[10px] left-[10px] w-full">
|
||||
<div className="relative top-[5px]">
|
||||
<div className="absolute top-[23px] right-[5%] grid grid-cols-[auto_auto] justify-items-end">
|
||||
<div className="flex justify-center items-center">
|
||||
<span className="text-[#FE564D] text-[9px]">▲</span>
|
||||
</div>
|
||||
<span className="ml-1 text-right">{dataInfo?.max}</span>
|
||||
<div className="flex justify-center items-center">
|
||||
<span className="text-[#27B148] text-[9px]">▼</span>
|
||||
</div>
|
||||
<span className="ml-1 text-right">{dataInfo?.min}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ECharts ref={chartRef} option={option} theme="apipark" style={{ height: height || 400 }} opts={{ renderer: 'svg' }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceAreaChart
|
||||
@@ -0,0 +1,273 @@
|
||||
import ECharts, { EChartsOption } from 'echarts-for-react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { chartColors, defaultColor } from '@common/const/charts/theme'
|
||||
|
||||
export type BarChartInfo = {
|
||||
title: string
|
||||
value: string
|
||||
date: string[]
|
||||
data: {
|
||||
name: string
|
||||
color: string
|
||||
value: number[]
|
||||
}[]
|
||||
showXAxis?: boolean
|
||||
}
|
||||
|
||||
type ServiceBarCharProps = {
|
||||
customClassNames?: string
|
||||
dataInfo?: BarChartInfo
|
||||
height?: number
|
||||
}
|
||||
|
||||
const ServiceBarChar = ({ customClassNames, dataInfo, height }: ServiceBarCharProps) => {
|
||||
const chartRef = useRef<ECharts>(null)
|
||||
const [option, setOption] = useState<EChartsOption | undefined>({})
|
||||
// 使用从主题配置中导入的默认颜色,而不是硬编码的颜色值
|
||||
const [detaultColor] = useState(defaultColor)
|
||||
const tokenMap = {
|
||||
inputToken: $t('输入 Token'),
|
||||
outputToken: $t('输出 Token'),
|
||||
totalToken: $t('总 Token')
|
||||
}
|
||||
const setChartOption = (dataInfo: BarChartInfo) => {
|
||||
const isNumberArray = typeof dataInfo.data[0] !== 'object'
|
||||
const legendData = isNumberArray ? [dataInfo.title] : dataInfo.data.map((item) => item.name)
|
||||
const hasData = dataInfo.data && dataInfo.data.length > 0
|
||||
const tooltipFormatter = (params: { name: string; color: string; seriesIndex?: number }) => {
|
||||
let tooltipContent = `<div style="min-width:140px;padding:8px;">
|
||||
<div>${isNumberArray ? '' : params.name}</div>`
|
||||
const data = isNumberArray
|
||||
? [
|
||||
{
|
||||
name: params.name,
|
||||
color: detaultColor,
|
||||
value: dataInfo.data
|
||||
}
|
||||
]
|
||||
: dataInfo.data
|
||||
// 为每个数据系列添加一行
|
||||
data.forEach((item, index) => {
|
||||
// 使用与柱状图相同的颜色策略,确保颜色一致性
|
||||
const color = index < chartColors.length ? chartColors[index] : item.color
|
||||
const name = tokenMap[item.name as keyof typeof tokenMap] || item.name
|
||||
const value = item.value[dataInfo.date.indexOf(params.name)] || 0
|
||||
|
||||
const marker = `<span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${color};"></span>`
|
||||
tooltipContent += `<div style="margin-top: ${index === 0 ? 8 : 4}px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>${marker} ${name}</div> <div style="font-weight:bold; margin-left: 20px;">${value}</div>
|
||||
</div>`
|
||||
})
|
||||
|
||||
tooltipContent += '</div>'
|
||||
return tooltipContent
|
||||
}
|
||||
const option: EChartsOption = {
|
||||
title: [
|
||||
{
|
||||
text: '{titleStyle|' + $t(dataInfo.title) + '}\n\n{valueStyle|' + dataInfo.value + '}',
|
||||
left: '2%',
|
||||
top: '0',
|
||||
textStyle: {
|
||||
rich: {
|
||||
titleStyle: {
|
||||
fontSize: 14,
|
||||
color: '#999',
|
||||
fontWeight: 'normal',
|
||||
lineHeight: 20
|
||||
},
|
||||
valueStyle: {
|
||||
fontSize: 32,
|
||||
color: '#101010',
|
||||
fontWeight: 500,
|
||||
lineHeight: 40
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '3%',
|
||||
bottom: '0%',
|
||||
top: '110px',
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: hasData ? {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
formatter: function (params: any) {
|
||||
// 如果是数组,取第一个参数的name
|
||||
const param = Array.isArray(params) ? params[0] : params
|
||||
return tooltipFormatter(param)
|
||||
}
|
||||
} : {
|
||||
show: false // 没有数据时不显示tooltip
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
data: legendData,
|
||||
right: '10px',
|
||||
top: '30px',
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
textStyle: {
|
||||
color: '#333'
|
||||
},
|
||||
icon: 'rect'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dataInfo.date,
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#ccc'
|
||||
}
|
||||
},
|
||||
show: false
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '',
|
||||
min: 0,
|
||||
minInterval: 1,
|
||||
show: hasData, // 没有数据时不显示Y轴
|
||||
splitLine: {
|
||||
show: hasData, // 没有数据时不显示网格线
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: '#eee'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: '{value}'
|
||||
}
|
||||
},
|
||||
// 添加数据缩放组件,实现鼠标放大缩小,后续可能需要
|
||||
// dataZoom: [
|
||||
// {
|
||||
// type: 'inside', // 内置的数据区域缩放组件(使用鼠标滚轮缩放)
|
||||
// xAxisIndex: 0, // 设置缩放作用在第一个x轴
|
||||
// filterMode: 'filter',
|
||||
// start: 0,
|
||||
// end: 100
|
||||
// },
|
||||
// {
|
||||
// type: 'slider', // 滑动条型数据区域缩放组件
|
||||
// xAxisIndex: 0,
|
||||
// filterMode: 'filter',
|
||||
// height: 20,
|
||||
// bottom: 0,
|
||||
// start: 0,
|
||||
// end: 100,
|
||||
// handleIcon: 'path://M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
|
||||
// handleSize: '80%',
|
||||
// handleStyle: {
|
||||
// color: '#fff',
|
||||
// shadowBlur: 3,
|
||||
// shadowColor: 'rgba(0, 0, 0, 0.6)',
|
||||
// shadowOffsetX: 2,
|
||||
// shadowOffsetY: 2
|
||||
// },
|
||||
// show: false // 默认隐藏底部的滑动条,可以改为 true 显示
|
||||
// }
|
||||
// ],
|
||||
// 添加空状态提示
|
||||
graphic: !hasData
|
||||
? [
|
||||
{
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'middle',
|
||||
style: {
|
||||
text: $t('暂无数据'),
|
||||
fontSize: 14,
|
||||
fill: '#999'
|
||||
}
|
||||
}
|
||||
]
|
||||
: [],
|
||||
series: isNumberArray
|
||||
? [
|
||||
{
|
||||
name: dataInfo.title,
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
itemStyle: {
|
||||
color: detaultColor
|
||||
},
|
||||
data: dataInfo.data
|
||||
}
|
||||
]
|
||||
: dataInfo.data.map((item, index) => ({
|
||||
name: item.name,
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
itemStyle: {
|
||||
// 使用主题中的颜色列表,如果索引超出范围则使用项目自带的颜色
|
||||
color: index < chartColors.length ? chartColors[index] : item.color
|
||||
},
|
||||
data: item.value
|
||||
}))
|
||||
}
|
||||
setOption(option)
|
||||
}
|
||||
|
||||
// 使用深度监听来确保图表数据更新
|
||||
useEffect(() => {
|
||||
if (!dataInfo) return
|
||||
|
||||
// 直接获取 ECharts 实例并设置选项
|
||||
const echartsInstance = chartRef.current?.getEchartsInstance()
|
||||
if (echartsInstance) {
|
||||
// 清除已有的图表
|
||||
echartsInstance.clear()
|
||||
// 重新设置选项
|
||||
setChartOption(dataInfo)
|
||||
}
|
||||
}, [dataInfo, JSON.stringify(dataInfo)])
|
||||
|
||||
// 添加窗口大小变化监听,实现自适应
|
||||
useEffect(() => {
|
||||
// 定义resize处理函数
|
||||
const handleResize = () => {
|
||||
const echartsInstance = chartRef.current?.getEchartsInstance()
|
||||
if (echartsInstance) {
|
||||
echartsInstance.resize()
|
||||
}
|
||||
}
|
||||
|
||||
// 添加监听
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
// 组件卸载时移除监听
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<div className={`w-full ${customClassNames}`}>
|
||||
<ECharts
|
||||
ref={chartRef}
|
||||
option={option}
|
||||
style={{ height: height || 400 }}
|
||||
opts={{ renderer: 'svg' }}
|
||||
theme="apipark" // 这里应用主题名称,需要先在应用入口注册
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceBarChar
|
||||
@@ -0,0 +1,37 @@
|
||||
import TimeRangeSelector, { RangeValue, TimeRange } from '@common/components/aoplatform/TimeRangeSelector'
|
||||
import { useState } from 'react'
|
||||
|
||||
export type TimeOption = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays'
|
||||
const DateSelectFilter = ({
|
||||
selectCallback,
|
||||
defaultTime,
|
||||
customClassNames
|
||||
}: {
|
||||
selectCallback: (timeRange: TimeRange) => void
|
||||
defaultTime: TimeOption
|
||||
customClassNames?: string
|
||||
}) => {
|
||||
/** 默认时间 */
|
||||
const [timeButton, setTimeButton] = useState<TimeOption>(defaultTime || 'hour')
|
||||
/** 日期选择 */
|
||||
const [datePickerValue, setDatePickerValue] = useState<RangeValue>()
|
||||
/** 时间范围变化 */
|
||||
const handleTimeRangeChange = (timeRange: TimeRange) => {
|
||||
selectCallback(timeRange)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TimeRangeSelector
|
||||
labelSize="small"
|
||||
customClassNames={customClassNames}
|
||||
initialTimeButton={timeButton}
|
||||
onTimeButtonChange={setTimeButton}
|
||||
initialDatePickerValue={datePickerValue}
|
||||
onTimeRangeChange={handleTimeRangeChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DateSelectFilter
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Button, Card } from 'antd'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { $t } from '@common/locales'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
|
||||
/** 服务指标 */
|
||||
type IndicatorType = {
|
||||
title: string
|
||||
link?: string
|
||||
content: string | React.ReactNode
|
||||
}
|
||||
const Indicator = ({ indicatorInfo }: { indicatorInfo: any }) => {
|
||||
/** 服务指标 */
|
||||
const [indicatorList, setIndicator] = useState<IndicatorType[]>([])
|
||||
/** 路由跳转 */
|
||||
const navigateTo = useNavigate()
|
||||
|
||||
/** 设置服务指标 */
|
||||
const setIndicatorList = () => {
|
||||
const side = indicatorInfo?.serviceKind === 'ai' ? 'aiInside' : 'inside'
|
||||
setIndicator([
|
||||
{
|
||||
title: indicatorInfo?.enableMcp ? 'APIs / Tools' : 'APIs',
|
||||
link: `/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/route`,
|
||||
content: indicatorInfo?.apiNum ?? 0
|
||||
},
|
||||
{
|
||||
title: $t('订阅数量'),
|
||||
link: `/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/subscriber`,
|
||||
content: indicatorInfo?.subscriberNum ?? 0
|
||||
},
|
||||
{
|
||||
title: 'MCP',
|
||||
link: `/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/setting`,
|
||||
content: (
|
||||
<>
|
||||
<Button
|
||||
color={indicatorInfo?.enableMcp ? 'green' : 'primary'}
|
||||
className="w-full rounded-[10px]"
|
||||
variant="outlined"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
navigateTo(`/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/setting`)
|
||||
}}
|
||||
>
|
||||
{indicatorInfo?.enableMcp ? $t('已开启') : $t('开启 MCP')}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!indicatorInfo) return
|
||||
setIndicatorList()
|
||||
}, [indicatorInfo])
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
{indicatorList.map((item, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 cursor-pointer rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'py-[20px] px-[18px]'
|
||||
}}
|
||||
onClick={() => {
|
||||
if (item.link) {
|
||||
navigateTo(item.link)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="text-[14px] text-[#888] mb-[10px]">
|
||||
{item.title}
|
||||
{item.link && <Icon icon="uiw:right" width="16" height="16" className="absolute top-[14px] right-[14px]" />}
|
||||
</div>
|
||||
<div className={`${index < 2 ? 'text-[32px] font-medium text-[#101010]' : 'block mt-[30px]'}`}>
|
||||
{item.content}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Indicator
|
||||
@@ -0,0 +1,92 @@
|
||||
import { useMemo, useRef, useEffect } from 'react'
|
||||
import PageList from '@common/components/aoplatform/PageList'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { Card } from 'antd'
|
||||
import { AI_SERVICE_TOP_RANKING_LIST, REST_SERVICE_TOP_RANKING_LIST } from '@core/const/system/const'
|
||||
|
||||
interface RankingListData {
|
||||
[key: string]: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
request: number;
|
||||
token?: number;
|
||||
traffic?: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface PageListRef {
|
||||
reload: () => void;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const RankingList = ({ topRankingList, serviceType }: { topRankingList: RankingListData; serviceType: 'aiService' | 'restService' }) => {
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
/** 表格 ref */
|
||||
const tableRefs = useRef<{ [key: string]: PageListRef | null }>({});
|
||||
/** 列 */
|
||||
const columns = useMemo(() => {
|
||||
return [...(serviceType === 'aiService' ? AI_SERVICE_TOP_RANKING_LIST : REST_SERVICE_TOP_RANKING_LIST)].map((x) => {
|
||||
return {
|
||||
...x,
|
||||
title: typeof x.title === 'string' ? $t(x.title as string) : x.title
|
||||
}
|
||||
})
|
||||
}, [serviceType, state.language])
|
||||
|
||||
/** 监听 serviceType 变化,刷新所有表格 */
|
||||
useEffect(() => {
|
||||
// 重新加载所有表格数据
|
||||
if (Object.keys(tableRefs.current).length > 0) {
|
||||
Object.values(tableRefs.current).forEach(ref => {
|
||||
// 如果组件实例存在并且有reload方法
|
||||
if (ref && typeof ref.reload === 'function') {
|
||||
ref.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [serviceType, topRankingList])
|
||||
|
||||
/**
|
||||
* 获取表格数据
|
||||
* @param item
|
||||
* @returns
|
||||
*/
|
||||
const getTableData = (item: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({ data: topRankingList[item], success: true })
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className="flex w-full pb-[10px]">
|
||||
{Object.keys(topRankingList)?.map((item: any, index: number) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 min-w-[430px] h-fit rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'p-[15px]'
|
||||
}}
|
||||
>
|
||||
<div className="mb-[10px]">
|
||||
<span className="text-[14px] text-[#888]" style={{ fontFamily: 'Microsoft YaHei' }}>{item === 'TOP API' ? $t('API 使用排名') : $t('消费者使用排名')}</span>
|
||||
</div>
|
||||
<PageList
|
||||
id={item}
|
||||
columns={[...columns]}
|
||||
minVirtualHeight={430}
|
||||
noScroll
|
||||
request={() => getTableData(item)}
|
||||
showPagination={false}
|
||||
tableClass="ranking-list"
|
||||
ref={ref => {
|
||||
if (ref) tableRefs.current[item] = ref;
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RankingList
|
||||
@@ -0,0 +1,361 @@
|
||||
import { Card, Spin } from 'antd'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import Indicator from './indicator/Indicator'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import DateSelectFilter, { TimeOption } from './filter/DateSelectFilter'
|
||||
import { TimeRange } from '@common/components/aoplatform/TimeRangeSelector'
|
||||
import ServiceBarChar, { BarChartInfo } from './charts/ServiceBarChar'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { App } from 'antd'
|
||||
import ServiceAreaChart from './charts/ServiceAreaChart'
|
||||
import RankingList from './rankingList/RankingList'
|
||||
import { getTime } from '@dashboard/utils/dashboard'
|
||||
import { setBarChartInfoData } from './utils'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
const ServiceOverview = ({ serviceType }: { serviceType: 'aiService' | 'restService' }) => {
|
||||
/** 路由参数 */
|
||||
const { serviceId, teamId } = useParams<{ serviceId: string; teamId: string }>()
|
||||
/** 面板 loading */
|
||||
const [dashboardLoading, setDashboardLoading] = useState(true)
|
||||
/** 默认时间 */
|
||||
const [defaultTime] = useState<TimeOption>('sevenDays')
|
||||
/** 当前选中的时间范围 */
|
||||
const [timeRange, setTimeRange] = useState<TimeRange | undefined>()
|
||||
/** 总数数据 */
|
||||
const [barChartInfo, setBarChartInfo] = useState<any>()
|
||||
/** 平均值数据 */
|
||||
const [perBarChartInfo, setPerBarChartInfo] = useState<any>()
|
||||
/** 指标数据 */
|
||||
const [indicatorInfo, setIndicatorInfo] = useState<any>([])
|
||||
/** 排名表格数据 */
|
||||
const [topRankingList, setTopRankingList] = useState<any>([])
|
||||
/** 获取服务信息 */
|
||||
const { fetchData } = useFetch()
|
||||
/** 弹窗组件 */
|
||||
const { message } = App.useApp()
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
/** AI 服务数据 */
|
||||
const [aiServiceOverview, setAiServiceOverview] = useState<any>()
|
||||
/** REST 服务数据 */
|
||||
const [restServiceOverview, setRestServiceOverview] = useState<any>()
|
||||
/** 时间选择回调 */
|
||||
const selectCallback = (date: TimeRange) => {
|
||||
setTimeRange(date)
|
||||
}
|
||||
|
||||
/** 获取 AI 服务信息 */
|
||||
const getAIServiceOverview = () => {
|
||||
fetchData<BasicResponse<{ overview: any }>>('service/overview/monitor/ai', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId, team: teamId, start: timeRange?.start, end: timeRange?.end },
|
||||
eoTransformKeys: [
|
||||
'enable_mcp',
|
||||
'subscriber_num',
|
||||
'api_num',
|
||||
'service_kind',
|
||||
'avaliable_monitor',
|
||||
'request_overview',
|
||||
'token_overview',
|
||||
'avg_token_overview',
|
||||
'avg_request_per_subscriber_overview',
|
||||
'avg_token_per_subscriber_overview',
|
||||
'request_total',
|
||||
'token_total',
|
||||
'avg_token',
|
||||
'max_token',
|
||||
'min_token',
|
||||
'avg_request_per_subscriber',
|
||||
'avg_token_per_subscriber',
|
||||
'input_token',
|
||||
'output_token',
|
||||
'total_token'
|
||||
]
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 存储 AI 服务数据
|
||||
setAiServiceOverview(data.overview)
|
||||
// 设置 AI 报表数据
|
||||
setAiChartInfoData(data.overview)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
setDashboardLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 REST 服务数据
|
||||
* */
|
||||
const setRestChartInfoData = (serviceOverview: any) => {
|
||||
// 设置指标数据
|
||||
setIndicatorInfo({
|
||||
apiNum: serviceOverview.apiNum,
|
||||
subscriberNum: serviceOverview.subscriberNum,
|
||||
teamId: teamId,
|
||||
enableMcp: serviceOverview.enableMcp,
|
||||
serviceKind: serviceOverview.serviceKind,
|
||||
serviceId: serviceId
|
||||
})
|
||||
// 设置总数数据
|
||||
setBarChartInfo([
|
||||
// 服务请求次数
|
||||
setBarChartInfoData({
|
||||
title: $t('请求数'),
|
||||
data: serviceOverview.requestOverview,
|
||||
value: serviceOverview.requestTotal,
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
// 流量消耗总数
|
||||
setBarChartInfoData({
|
||||
title: $t('流量'),
|
||||
data: serviceOverview.trafficOverview,
|
||||
value: serviceOverview.trafficTotal,
|
||||
date: serviceOverview.date
|
||||
})
|
||||
])
|
||||
// 设置平均值数据
|
||||
setPerBarChartInfo([
|
||||
// 各个模型使用量
|
||||
{
|
||||
title: $t('平均响应时间'),
|
||||
data: serviceOverview.avgResponseTimeOverview,
|
||||
value: serviceOverview.avgResponseTime,
|
||||
date: serviceOverview.date,
|
||||
max: serviceOverview.maxResponseTime,
|
||||
min: serviceOverview.minResponseTime,
|
||||
type: 'area',
|
||||
showXAxis: false
|
||||
},
|
||||
// 平均请求
|
||||
setBarChartInfoData({
|
||||
title: $t('平均请求数'),
|
||||
data: serviceOverview.avgRequestPerSubscriberOverview,
|
||||
value: serviceOverview.avgRequestPerSubscriber,
|
||||
date: serviceOverview.date,
|
||||
showXAxis: false
|
||||
}),
|
||||
// 平均流量消耗
|
||||
setBarChartInfoData({
|
||||
title: $t('平均流量'),
|
||||
data: serviceOverview.avgTrafficPerSubscriberOverview,
|
||||
value: serviceOverview.avgTrafficPerSubscriber,
|
||||
date: serviceOverview.date,
|
||||
showXAxis: false
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 AI 服务数据
|
||||
* */
|
||||
const setAiChartInfoData = (serviceOverview: any) => {
|
||||
// 设置指标数据
|
||||
setIndicatorInfo({
|
||||
apiNum: serviceOverview.apiNum,
|
||||
subscriberNum: serviceOverview.subscriberNum,
|
||||
teamId: teamId,
|
||||
enableMcp: serviceOverview.enableMcp,
|
||||
serviceKind: serviceOverview.serviceKind,
|
||||
serviceId: serviceId
|
||||
})
|
||||
// 设置总数数据
|
||||
setBarChartInfo([
|
||||
// 服务请求次数
|
||||
setBarChartInfoData({
|
||||
title: $t('请求数'),
|
||||
data: serviceOverview.requestOverview,
|
||||
value: serviceOverview.requestTotal,
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
// token 消耗总数
|
||||
setBarChartInfoData({
|
||||
title: $t('Token'),
|
||||
data: serviceOverview.tokenOverview.map((item: { inputToken: number; outputToken: number }) => ({
|
||||
inputToken: item.inputToken,
|
||||
outputToken: item.outputToken
|
||||
})),
|
||||
value: serviceOverview.tokenTotal,
|
||||
date: serviceOverview.date
|
||||
})
|
||||
])
|
||||
// 设置平均值数据
|
||||
setPerBarChartInfo([
|
||||
// 平均 token 消耗
|
||||
{
|
||||
title: $t('平均 Token/s 统计'),
|
||||
data: serviceOverview.avgTokenOverview,
|
||||
value: serviceOverview.avgToken,
|
||||
date: serviceOverview.date,
|
||||
min: serviceOverview.minToken,
|
||||
max: serviceOverview.maxToken,
|
||||
type: 'area'
|
||||
},
|
||||
// 平均请求
|
||||
setBarChartInfoData({
|
||||
title: $t('平均请求数'),
|
||||
data: serviceOverview.avgRequestPerSubscriberOverview,
|
||||
value: serviceOverview.avgRequestPerSubscriber,
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
// 评价 token 消耗
|
||||
setBarChartInfoData({
|
||||
title: $t('平均 Token/订阅者统计'),
|
||||
data: serviceOverview.avgTokenPerSubscriberOverview.map((item: { inputToken: number; outputToken: number }) => ({
|
||||
inputToken: item.inputToken,
|
||||
outputToken: item.outputToken
|
||||
})),
|
||||
value: serviceOverview.avgTokenPerSubscriber,
|
||||
date: serviceOverview.date
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
/** 获取 REST 服务信息 */
|
||||
const getRestServiceOverview = () => {
|
||||
fetchData<BasicResponse<{ overview: any }>>('service/overview/monitor/rest', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId, team: teamId, start: timeRange?.start, end: timeRange?.end },
|
||||
eoTransformKeys: [
|
||||
'enable_mcp',
|
||||
'subscriber_num',
|
||||
'api_num',
|
||||
'service_kind',
|
||||
'avaliable_monitor',
|
||||
'request_overview',
|
||||
'traffic_overview',
|
||||
'avg_request_per_subscriber_overview',
|
||||
'avg_response_time_overview',
|
||||
'avg_traffic_per_subscriber_overview',
|
||||
'request_total',
|
||||
'traffic_total',
|
||||
'max_response_time',
|
||||
'min_response_time',
|
||||
'avg_response_time',
|
||||
'avg_request_per_subscriber',
|
||||
'avg_traffic_per_subscriber'
|
||||
]
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 存储 REST 服务数据
|
||||
setRestServiceOverview(data.overview)
|
||||
// 设置 REST 报表数据
|
||||
setRestChartInfoData(data.overview)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
setDashboardLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取排名列表 */
|
||||
const getTopRankingList = () => {
|
||||
fetchData<BasicResponse<{ overview: any }>>('service/monitor/top10', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId, team: teamId, start: timeRange?.start, end: timeRange?.end }
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 设置排名表格数据
|
||||
setTopRankingList({
|
||||
'TOP API': data.apis,
|
||||
'TOP Consumer': data.consumers
|
||||
})
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
setDashboardLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const { startTime, endTime } = getTime(defaultTime, [])
|
||||
setTimeRange({
|
||||
start: startTime,
|
||||
end: endTime
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (timeRange) {
|
||||
serviceType === 'aiService' ? getAIServiceOverview() : getRestServiceOverview()
|
||||
getTopRankingList()
|
||||
}
|
||||
}, [timeRange])
|
||||
|
||||
useEffect(() => {
|
||||
if (serviceType === 'aiService') {
|
||||
aiServiceOverview && setAiChartInfoData(aiServiceOverview)
|
||||
} else {
|
||||
restServiceOverview && setRestChartInfoData(restServiceOverview)
|
||||
}
|
||||
}, [state.language])
|
||||
|
||||
return (
|
||||
<Spin
|
||||
className="h-full pb-[20px]"
|
||||
wrapperClassName="h-full min-h-[150px]"
|
||||
indicator={
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ transform: 'scale(1.5)' }}>
|
||||
<LoadingOutlined style={{ fontSize: 30 }} spin />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
spinning={dashboardLoading}
|
||||
>
|
||||
<div className="mr-[30px]">
|
||||
<Indicator indicatorInfo={indicatorInfo} />
|
||||
<div className="mt-[20px]">
|
||||
<DateSelectFilter selectCallback={selectCallback} defaultTime={defaultTime} />
|
||||
</div>
|
||||
<div className="mt-[20px] flex mb-[10px]">
|
||||
{barChartInfo?.map((item: BarChartInfo, index: number) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 min-w-[430px] rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'py-[15px] px-[0px]'
|
||||
}}
|
||||
>
|
||||
<ServiceBarChar key={index} height={400} dataInfo={item} customClassNames="flex-1"></ServiceBarChar>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex mb-[10px]">
|
||||
{perBarChartInfo?.map((item: any, index: number) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 rounded-[10px] min-w-[284px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'py-[15px] px-[0px]'
|
||||
}}
|
||||
>
|
||||
{item.type === 'area' ? (
|
||||
<>
|
||||
<ServiceAreaChart
|
||||
key={index}
|
||||
height={270}
|
||||
dataInfo={item}
|
||||
customClassNames="flex-1 relative"
|
||||
></ServiceAreaChart>
|
||||
</>
|
||||
) : (
|
||||
<ServiceBarChar key={index} height={270} dataInfo={item} customClassNames="flex-1"></ServiceBarChar>
|
||||
)}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<RankingList topRankingList={topRankingList} serviceType={serviceType} />
|
||||
</div>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceOverview
|
||||
@@ -0,0 +1,65 @@
|
||||
export type BarData = {
|
||||
title: string
|
||||
value: string
|
||||
date: string[]
|
||||
data: any[]
|
||||
showXAxis?: boolean
|
||||
}
|
||||
export const setBarChartInfoData = ({ title, value, data, date, showXAxis }: BarData) => {
|
||||
// 首先获取所有的键名(假设所有对象的键名都一样)
|
||||
if (data.length === 0) {
|
||||
return {
|
||||
title,
|
||||
value,
|
||||
date,
|
||||
data: [],
|
||||
showXAxis: !!showXAxis
|
||||
}
|
||||
}
|
||||
if (typeof data[0] !== 'object') {
|
||||
return {
|
||||
title,
|
||||
value,
|
||||
date,
|
||||
data,
|
||||
showXAxis: !!showXAxis
|
||||
}
|
||||
}
|
||||
// 从第一个对象中获取所有键名
|
||||
const keys = Object.keys(data[0])
|
||||
// 定义颜色映射
|
||||
const colorMap: Record<string, string> = {
|
||||
'2xx': '#7EC26A',
|
||||
'4xx': '#F2CF59',
|
||||
'5xx': '#F17975',
|
||||
inputToken:'#7EC26A',
|
||||
outputToken: '#F2CF59',
|
||||
totalToken: '#F17975',
|
||||
'200': '#7EC26A',
|
||||
'400': '#F2CF59',
|
||||
'500': '#F17975'
|
||||
}
|
||||
|
||||
// 为每个键创建一个数据集
|
||||
const transformedData = keys.map((key, index) => {
|
||||
// 为没有映射颜色的键生成随机颜色
|
||||
const color =
|
||||
colorMap[key] ||
|
||||
`#${Math.floor(Math.random() * 16777215)
|
||||
.toString(16)
|
||||
.padStart(6, '0')}`
|
||||
|
||||
return {
|
||||
name: key,
|
||||
color: color,
|
||||
value: data.map((item) => item[key])
|
||||
}
|
||||
})
|
||||
return {
|
||||
title,
|
||||
value,
|
||||
date,
|
||||
data: transformedData,
|
||||
showXAxis: !!showXAxis
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { LoadingOutlined } from '@ant-design/icons'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { BasicResponse, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { EntityItem, MemberItem, SimpleTeamItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
@@ -49,7 +48,6 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
const { fetchData } = useFetch()
|
||||
const [teamOptionList, setTeamOptionList] = useState<DefaultOptionType[]>()
|
||||
const navigate = useNavigate()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { setSystemInfo } = useSystemContext()
|
||||
const [showClassify, setShowClassify] = useState<boolean>(true)
|
||||
const [showAI, setShowAI] = useState<boolean>(false)
|
||||
@@ -355,15 +353,6 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
|
||||
if (serviceId !== undefined) {
|
||||
setOnEdit(true)
|
||||
getSystemInfo()
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigate('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('设置')
|
||||
}
|
||||
])
|
||||
} else {
|
||||
getProviderOptionList()
|
||||
setOnEdit(false)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { EntityItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes'
|
||||
@@ -10,7 +9,7 @@ import { App, Button } from 'antd'
|
||||
import hljs from 'highlight.js'
|
||||
import 'highlight.js/styles/default.css'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
const ServiceInsideDocument = () => {
|
||||
const { message } = App.useApp()
|
||||
const [updater, setUpdater] = useState<string>()
|
||||
@@ -19,8 +18,6 @@ const ServiceInsideDocument = () => {
|
||||
const [doc, setDoc] = useState<string>()
|
||||
const { fetchData } = useFetch()
|
||||
const { serviceId, teamId } = useParams<RouterParams>()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const navigator = useNavigate()
|
||||
const save = () => {
|
||||
fetchData<
|
||||
BasicResponse<{
|
||||
@@ -88,15 +85,6 @@ const ServiceInsideDocument = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('使用说明')
|
||||
}
|
||||
])
|
||||
getServiceDoc()
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import { FC, useEffect, useMemo, useState } from 'react'
|
||||
import { Link, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
import { SystemConfigFieldType } from '../../const/system/type.ts'
|
||||
import { useSystemContext } from '../../contexts/SystemContext.tsx'
|
||||
import ServiceInfoCard from '@common/components/aoplatform/serviceInfoCard.tsx'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
|
||||
const SystemInsidePage: FC = () => {
|
||||
const { message } = App.useApp()
|
||||
@@ -26,12 +28,13 @@ const SystemInsidePage: FC = () => {
|
||||
const [activeMenu, setActiveMenu] = useState<string>()
|
||||
const navigateTo = useNavigate()
|
||||
const [showMenu, setShowMenu] = useState<boolean>(false)
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
|
||||
const getSystemInfo = () => {
|
||||
fetchData<BasicResponse<{ service: SystemConfigFieldType }>>('service/info', {
|
||||
method: 'GET',
|
||||
eoParams: { team: teamId, service: serviceId }
|
||||
}).then(response => {
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setSystemInfo(data.service)
|
||||
@@ -47,7 +50,7 @@ const SystemInsidePage: FC = () => {
|
||||
fetchData<BasicResponse<{ prefix: string; force: boolean }>>('service/router/define', {
|
||||
method: 'GET',
|
||||
eoParams: { service: serviceId, team: teamId }
|
||||
}).then(response => {
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setApiPrefix(data.prefix)
|
||||
@@ -65,6 +68,7 @@ const SystemInsidePage: FC = () => {
|
||||
'assets',
|
||||
null,
|
||||
[
|
||||
getItem(<Link to="./overview">{$t('总览')}</Link>, 'overview', undefined, undefined, undefined, ''),
|
||||
getItem(
|
||||
<Link to="./route">{$t('API 路由')}</Link>,
|
||||
'route',
|
||||
@@ -146,9 +150,10 @@ const SystemInsidePage: FC = () => {
|
||||
null,
|
||||
[
|
||||
// APP_MODE === 'pro' ? getItem(<Link to="./topology">{$t('调用拓扑图')}</Link>, 'topology',undefined,undefined,undefined,'project.mySystem.topology.view'):null,
|
||||
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting', undefined, undefined, undefined, ''),
|
||||
getItem(
|
||||
<Link to="./setting">{$t('设置')}</Link>,
|
||||
'setting',
|
||||
<Link to="./logs">{$t('日志')}</Link>,
|
||||
'logs',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
@@ -166,12 +171,11 @@ const SystemInsidePage: FC = () => {
|
||||
const newMenu = cloneDeep(menu)
|
||||
return newMenu!.filter((m: MenuItemGroupType) => {
|
||||
if (m && m.children && m.children.length > 0) {
|
||||
m.children = m.children.filter(c => {
|
||||
m.children = m.children.filter((c) => {
|
||||
if (!c) return false
|
||||
return (c as MenuItemType & { access: string }).access
|
||||
? checkPermission(
|
||||
(c as MenuItemType & { access: string })
|
||||
.access as keyof (typeof PERMISSION_DEFINITION)[0]
|
||||
(c as MenuItemType & { access: string }).access as keyof (typeof PERMISSION_DEFINITION)[0]
|
||||
)
|
||||
: true
|
||||
})
|
||||
@@ -180,12 +184,8 @@ const SystemInsidePage: FC = () => {
|
||||
})
|
||||
}
|
||||
const filteredMenu = filterMenu(SYSTEM_PAGE_MENU_ITEMS as MenuItemGroupType<MenuItemType>[])
|
||||
const menu =
|
||||
(activeMenu ?? filteredMenu[0]?.children)
|
||||
? filteredMenu[0]?.children?.[0]?.key
|
||||
: filteredMenu[0]?.key
|
||||
if (menu && currentUrl.split('/')[-1] !== menu)
|
||||
navigateTo(`/service/${teamId}/inside/${serviceId}/${menu}`)
|
||||
const menu = (activeMenu ?? filteredMenu[0]?.children) ? filteredMenu[0]?.children?.[0]?.key : filteredMenu[0]?.key
|
||||
if (menu && currentUrl.split('/')[-1] !== menu) navigateTo(`/service/${teamId}/inside/${serviceId}/${menu}`)
|
||||
return filteredMenu || []
|
||||
}, [accessData, accessInit, SYSTEM_PAGE_MENU_ITEMS])
|
||||
|
||||
@@ -208,7 +208,7 @@ const SystemInsidePage: FC = () => {
|
||||
} else if (serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]) {
|
||||
setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1])
|
||||
} else {
|
||||
setActiveMenu('route')
|
||||
setActiveMenu('overview')
|
||||
}
|
||||
}, [currentUrl])
|
||||
|
||||
@@ -219,10 +219,19 @@ const SystemInsidePage: FC = () => {
|
||||
}, [accessData])
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigateTo('/service/list')
|
||||
},
|
||||
{
|
||||
title: systemInfo?.name || ''
|
||||
}
|
||||
])
|
||||
if (activeMenu && serviceId === currentUrl.split('/')[currentUrl.split('/').length - 1]) {
|
||||
navigateTo(`/service/${teamId}/inside/${serviceId}/${activeMenu}`)
|
||||
}
|
||||
}, [activeMenu])
|
||||
}, [activeMenu, systemInfo, state.language])
|
||||
|
||||
useEffect(() => {
|
||||
serviceId && getSystemInfo()
|
||||
@@ -233,16 +242,7 @@ const SystemInsidePage: FC = () => {
|
||||
{showMenu ? (
|
||||
<InsidePage
|
||||
pageTitle={systemInfo?.name || '-'}
|
||||
tagList={[
|
||||
...(systemInfo?.enable_mcp ? [{ label: 'MCP', color: '#FFF0C1', className: 'text-[#000]' }] : []),
|
||||
{
|
||||
label: (
|
||||
<Paragraph className="mb-0" copyable={serviceId ? { text: serviceId } : false}>
|
||||
{$t('服务 ID')}:{serviceId || '-'}
|
||||
</Paragraph>
|
||||
)
|
||||
}
|
||||
]}
|
||||
customBanner={<ServiceInfoCard serviceId={serviceId} teamId={teamId} />}
|
||||
backUrl="/service/list"
|
||||
>
|
||||
<div className="flex flex-1 h-full">
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
STATUS_CODE
|
||||
} from '@common/const/const.tsx'
|
||||
import { SimpleMemberItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
@@ -20,7 +19,7 @@ import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import { App, Form, TreeSelect } from 'antd'
|
||||
import { DefaultOptionType } from 'antd/es/cascader'
|
||||
import { FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { SYSTEM_SUBSCRIBER_TABLE_COLUMNS } from '../../const/system/const.tsx'
|
||||
import {
|
||||
SimpleSystemItem,
|
||||
@@ -31,7 +30,6 @@ import {
|
||||
} from '../../const/system/type.ts'
|
||||
|
||||
const SystemInsideSubscriber: FC = () => {
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal, message } = App.useApp()
|
||||
const { fetchData } = useFetch()
|
||||
const { serviceId, teamId } = useParams<RouterParams>()
|
||||
@@ -39,7 +37,6 @@ const SystemInsideSubscriber: FC = () => {
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
|
||||
const { accessData, state } = useGlobalContext()
|
||||
const navigator = useNavigate()
|
||||
const getSystemSubscriber = () => {
|
||||
return fetchData<BasicResponse<{ subscribers: SystemSubscriberTableListItem[] }>>(
|
||||
'service/subscribers',
|
||||
@@ -162,15 +159,6 @@ const SystemInsideSubscriber: FC = () => {
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('订阅方管理')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId])
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ZoomInOutlined, ZoomOutOutlined } from '@ant-design/icons'
|
||||
import G6, { EdgeConfig, Graph, NodeConfig } from '@antv/g6'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { EntityItem } from '@common/const/type'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { $t } from '@common/locales'
|
||||
import { getNodeSpacing } from '@common/utils/systemRunning'
|
||||
@@ -10,7 +9,7 @@ import { RouterParams } from '@core/components/aoplatform/RenderRoutes'
|
||||
import { App, Button } from 'antd'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { RELATIVE_PICTURE_NODE_FONTSIZE } from '../../const/system-running/const'
|
||||
import { GraphData } from '../../const/system-running/type'
|
||||
import { SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP } from '../../const/system/const'
|
||||
@@ -26,9 +25,7 @@ export default function SystemTopology() {
|
||||
const [graph, setGraph] = useState<Graph | null>(null)
|
||||
const { fetchData } = useFetch()
|
||||
const { systemInfo } = useSystemContext()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [zoomNum, setZoomNum] = useState<number>(1)
|
||||
const navigate = useNavigate()
|
||||
|
||||
|
||||
const getNodeData = () => {
|
||||
@@ -105,15 +102,6 @@ export default function SystemTopology() {
|
||||
|
||||
useEffect(() => {
|
||||
getNodeData()
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigate('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('调用拓扑图')
|
||||
}
|
||||
])
|
||||
}, [serviceId])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -9,13 +9,12 @@ import { $t } from '@common/locales/index.ts'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import { Button, Empty, Spin, Upload, message } from 'antd'
|
||||
import { forwardRef, useEffect, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import {
|
||||
SystemApiDetail,
|
||||
SystemInsideApiDocumentHandle,
|
||||
SystemInsideApiDocumentProps
|
||||
} from '../../../const/system/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
|
||||
const SystemInsideApiDocument = forwardRef<
|
||||
SystemInsideApiDocumentHandle,
|
||||
@@ -26,18 +25,7 @@ const SystemInsideApiDocument = forwardRef<
|
||||
const [apiDetail, setApiDetail] = useState<SystemApiDetail>()
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [showEditor, setShowEditor] = useState<boolean>(false)
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const navigator = useNavigate()
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('API 文档')
|
||||
}
|
||||
])
|
||||
getApiDetail()
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
SystemInsideRouterCreateHandle,
|
||||
SystemInsideRouterCreateProps
|
||||
} from '../../../const/system/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
|
||||
const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, SystemInsideRouterCreateProps>(
|
||||
(props, ref) => {
|
||||
@@ -39,14 +38,12 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
const { state } = useGlobalContext()
|
||||
const { apiPrefix, prefixForce } = useSystemContext()
|
||||
const navigator = useNavigate()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
|
||||
|
||||
const onFinish = () => {
|
||||
return Promise.all([proxyRef.current?.validate?.(), form.validateFields()]).then(([, formValue]) => {
|
||||
const body = {
|
||||
...formValue,
|
||||
path: `${prefixForce ? apiPrefix + '/' : ''}${formValue.path.trim()}${formValue.pathMatch === 'prefix' ? '/*' : ''}`,
|
||||
path: `${prefixForce ? apiPrefix + (!formValue.path?.trim() ? '': '/') : ''}${(formValue.path?.trim() || '')}${formValue.pathMatch === 'prefix' ? '/*' : ''}`,
|
||||
proxy: {
|
||||
...formValue.proxy,
|
||||
path: formValue.proxy.path
|
||||
@@ -118,7 +115,7 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
.then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const { disable, protocols, path, methods, description, match, proxy } = data.router
|
||||
const { disable, protocols, path, name, methods, description, match, proxy } = data.router
|
||||
let newPath = path
|
||||
let pathMatch = 'full'
|
||||
if (prefixForce && path?.startsWith(apiPrefix + '/')) {
|
||||
@@ -131,6 +128,7 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
form.setFieldsValue({
|
||||
disable,
|
||||
protocols,
|
||||
name,
|
||||
path: newPath,
|
||||
pathMatch,
|
||||
methods,
|
||||
@@ -147,19 +145,6 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('API'),
|
||||
onClick: () => navigator(`/service/${teamId}/inside/${serviceId}/route`)
|
||||
},
|
||||
{
|
||||
title: routeId ? $t('编辑 API') : $t('添加 API')
|
||||
}
|
||||
])
|
||||
if (routeId) {
|
||||
getRouterConfig()
|
||||
} else {
|
||||
@@ -253,6 +238,14 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item<SystemApiProxyFieldType>
|
||||
className="flex-1"
|
||||
label={$t('路由名称')}
|
||||
name="name"
|
||||
rules={[{ required: true, whitespace: true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<SystemApiProxyFieldType> label={$t('请求协议')} name="protocols" rules={[{ required: true }]}>
|
||||
<Select
|
||||
|
||||
@@ -3,7 +3,6 @@ import PageList, { PageProColumns } from '@common/components/aoplatform/PageList
|
||||
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx'
|
||||
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { SimpleMemberItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
@@ -11,13 +10,12 @@ import { checkAccess } from '@common/utils/permission.ts'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import { App, Divider, Typography } from 'antd'
|
||||
import { FC, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { SYSTEM_API_TABLE_COLUMNS } from '../../../const/system/const.tsx'
|
||||
import { SystemApiTableListItem } from '../../../const/system/type.ts'
|
||||
|
||||
const SystemInsideRouterList: FC = () => {
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal, message } = App.useApp()
|
||||
const [tableListDataSource, setTableListDataSource] = useState<SystemApiTableListItem[]>([])
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true)
|
||||
@@ -162,17 +160,6 @@ const SystemInsideRouterList: FC = () => {
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId])
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('路由')
|
||||
}
|
||||
])
|
||||
}, [state.language])
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return [...SYSTEM_API_TABLE_COLUMNS].map((x) => {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
|
||||
import {ActionType} from "@ant-design/pro-components";
|
||||
import {FC, useEffect, useMemo, useRef, useState} from "react";
|
||||
import {Link, useLocation, useNavigate, useParams} from "react-router-dom";
|
||||
import { useLocation, useParams} from "react-router-dom";
|
||||
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx";
|
||||
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {App, Button} from "antd";
|
||||
import {
|
||||
SUBSCRIBE_APPROVAL_INNER_DONE_TABLE_COLUMN,
|
||||
@@ -26,7 +25,6 @@ import { SubscribeApprovalInfoType } from "@common/const/approval/type.tsx";
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
const SystemInsideApprovalList:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal,message } = App.useApp()
|
||||
const {serviceId, teamId} = useParams<RouterParams>();
|
||||
const [init, setInit] = useState<boolean>(true)
|
||||
@@ -40,7 +38,6 @@ const SystemInsideApprovalList:FC = ()=>{
|
||||
const [approvalBtnLoading,setApprovalBtnLoading] = useState<boolean>(false)
|
||||
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
|
||||
const {accessData,state} = useGlobalContext()
|
||||
const navigator = useNavigate()
|
||||
|
||||
const openModal = async (type:'approval'|'view',entity:SubscribeApprovalTableListItem)=>{
|
||||
message.loading($t(RESPONSE_TIPS.loading))
|
||||
@@ -142,15 +139,6 @@ const SystemInsideApprovalList:FC = ()=>{
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigator('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('订阅审核')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
|
||||
import { Tabs } from "antd"
|
||||
import { useState, useEffect, FC, useMemo } from "react"
|
||||
import { Link, Outlet, useLocation, useNavigate } from "react-router-dom"
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom"
|
||||
import { SYSTEM_PUBLISH_TAB_ITEMS } from "../../../const/system/const"
|
||||
import { $t } from "@common/locales"
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
|
||||
|
||||
const SystemInsidePublic:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const query =new URLSearchParams(useLocation().search)
|
||||
const location = useLocation()
|
||||
const currentUrl = location.pathname
|
||||
@@ -25,18 +23,6 @@ const SystemInsidePublic:FC = ()=>{
|
||||
setPageStatus(Number(query.get('status') ||0) as 0|1)
|
||||
}, [currentUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigateTo('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('发布')
|
||||
}
|
||||
])
|
||||
}, []);
|
||||
|
||||
const tabItems = useMemo(()=>SYSTEM_PUBLISH_TAB_ITEMS?.map((x)=>({...x, label:$t(x.label as string) })),[state.language])
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { ActionType, ParamsType } from "@ant-design/pro-components";
|
||||
import { App, Button, Divider } from "antd";
|
||||
import { useState, useRef, useEffect, useMemo, FC } from "react";
|
||||
import { useParams, Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { useParams, useLocation } from "react-router-dom";
|
||||
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList";
|
||||
import { PublishApprovalModalContent } from "@common/components/aoplatform/PublishApprovalModalContent";
|
||||
import { PUBLISH_APPROVAL_RECORD_INNER_TABLE_COLUMN, PUBLISH_APPROVAL_VERSION_INNER_TABLE_COLUMN, PublishApplyStatusEnum, PublishStatusEnum, PublishTableStatusColorClass } from "@common/const/approval/const";
|
||||
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
|
||||
import { RouterParams, SimpleMemberItem } from "@common/const/type.ts";
|
||||
import { MemberTableListItem } from "../../../const/member/type";
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext";
|
||||
import { useFetch } from "@common/hooks/http";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission";
|
||||
import { SystemPublishReleaseItem } from "../../../const/system/type";
|
||||
@@ -22,7 +21,6 @@ import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
const SystemInsidePublicList:FC = ()=>{
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal,message } = App.useApp()
|
||||
const pageListRef = useRef<ActionType>(null);
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true);
|
||||
@@ -44,7 +42,6 @@ const SystemInsidePublicList:FC = ()=>{
|
||||
const [drawerData, setDrawerData] = useState<PublishTableListItem|PublishVersionTableListItem >({} as PublishTableListItem)
|
||||
const [drawerOkTitle, setDrawerOkTitle] = useState<string>('确认')
|
||||
const [isOkToPublish, setIsOkToPublish] = useState<boolean>(false)
|
||||
const navigateTo = useNavigate()
|
||||
const getSystemPublishList = (params?: ParamsType & {
|
||||
pageSize?: number | undefined;
|
||||
current?: number | undefined;
|
||||
@@ -350,15 +347,6 @@ const SystemInsidePublicList:FC = ()=>{
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigateTo('/service/list')
|
||||
},
|
||||
{
|
||||
title:$t('发布')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
@@ -8,11 +8,10 @@ import EditableTable from "@common/components/aoplatform/EditableTable.tsx";
|
||||
import EditableTableWithModal from "@common/components/aoplatform/EditableTableWithModal.tsx";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { UPSTREAM_TYPE_OPTIONS, SYSTEM_UPSTREAM_GLOBAL_CONFIG_TABLE_COLUMNS, schemeOptions, UPSTREAM_BALANCE_OPTIONS, UPSTREAM_PASS_HOST_OPTIONS, PROXY_HEADER_CONFIG, UPSTREAM_PROXY_HEADER_TYPE_OPTIONS } from "../../../const/system/const.tsx";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx";
|
||||
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE } from "@common/const/const.tsx";
|
||||
import { useFetch } from "@common/hooks/http.ts";
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
|
||||
|
||||
@@ -33,10 +32,8 @@ const SystemInsideUpstreamContent= forwardRef<SystemInsideUpstreamContentHandle>
|
||||
const {fetchData} = useFetch()
|
||||
const [, forceUpdate] = useState<unknown>(null);
|
||||
const [formShowHost, setFormShowHost] = useState<boolean>(false);
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [form] = Form.useForm();
|
||||
const {state} = useGlobalContext()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
save:()=>formRef.current?.save()
|
||||
@@ -109,16 +106,7 @@ const globalConfigNodesRule: FormItemProps['rules'] = [
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务'),
|
||||
onClick: () => navigate('/service/list')
|
||||
},
|
||||
{
|
||||
title: $t('上游')
|
||||
}])
|
||||
|
||||
getUpstreamInfo();
|
||||
getUpstreamInfo();
|
||||
}, [serviceId]);
|
||||
|
||||
const typeOptions = useMemo(()=>UPSTREAM_TYPE_OPTIONS.map(x=>({...x, label:$t(x.label)})),[state.language])
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useFetch } from '@common/hooks/http.ts'
|
||||
import { DefaultOptionType } from 'antd/es/cascader'
|
||||
import { TeamConfigFieldType } from '../../const/team/type.ts'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useTeamContext } from '../../contexts/TeamContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
@@ -32,7 +31,6 @@ const TeamConfig = forwardRef<TeamConfigHandle, TeamConfigProps>((props, ref) =>
|
||||
const currentUrl = location.pathname
|
||||
const { fetchData } = useFetch()
|
||||
const [managerOption, setManagerOption] = useState<DefaultOptionType[]>([])
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { setTeamInfo } = useTeamContext()
|
||||
const { checkPermission, accessInit, state } = useGlobalContext()
|
||||
const pageType = useMemo(() => {
|
||||
@@ -133,15 +131,6 @@ const TeamConfig = forwardRef<TeamConfigHandle, TeamConfigProps>((props, ref) =>
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('团队'),
|
||||
onClick: () => navigateTo('/team/list')
|
||||
},
|
||||
{ title: $t('设置') }
|
||||
])
|
||||
}, [state.language])
|
||||
useEffect(() => {
|
||||
|
||||
getManagerList()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx'
|
||||
import { ActionType } from '@ant-design/pro-components'
|
||||
import { FC, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { App, Button, Modal, Select } from 'antd'
|
||||
import { BasicResponse, COLUMNS_TITLE, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
@@ -59,7 +58,6 @@ export const addMemberToDepartment = (
|
||||
|
||||
const TeamInsideMember: FC = () => {
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal, message } = App.useApp()
|
||||
const { fetchData } = useFetch()
|
||||
const { teamId } = useParams<RouterParams>()
|
||||
@@ -73,7 +71,6 @@ const TeamInsideMember: FC = () => {
|
||||
const [addMemberBtnDisabled, setAddMemberBtnDisabled] = useState<boolean>(true)
|
||||
const [allMemberSelectedDepartIds, setAllMemberSelectedDepartIds] = useState<string[]>([])
|
||||
const [roleList, setRoleList] = useState<EntityItem[]>([])
|
||||
const navigator = useNavigate()
|
||||
|
||||
const operation: PageProColumns<TeamMemberTableListItem>[] = [
|
||||
{
|
||||
@@ -355,13 +352,6 @@ const TeamInsideMember: FC = () => {
|
||||
}, [teamId])
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('团队'),
|
||||
onClick: () => navigator('/team/list')
|
||||
},
|
||||
{ title: $t('成员') }
|
||||
])
|
||||
getRoleList()
|
||||
}, [state.language])
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { PERMISSION_DEFINITION } from "@common/const/permissions.ts";
|
||||
import { TeamConfigType } from "@core/const/team/type.ts";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
import { getItem } from "@common/utils/navigation.tsx";
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx";
|
||||
|
||||
const TeamInsidePage:FC = ()=> {
|
||||
const { message } = App.useApp()
|
||||
@@ -26,6 +27,7 @@ const TeamInsidePage:FC = ()=> {
|
||||
const {getTeamAccessData,cleanTeamAccessData,accessData,checkPermission,teamDataFlushed,accessInit,state} = useGlobalContext()
|
||||
const navigateTo = useNavigate()
|
||||
const [activeMenu, setActiveMenu] = useState<string>()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
|
||||
const onMenuClick: MenuProps['onClick'] = ({key}) => {
|
||||
setActiveMenu(key)
|
||||
@@ -88,6 +90,16 @@ const TeamInsidePage:FC = ()=> {
|
||||
}
|
||||
},[activeMenu])
|
||||
|
||||
useEffect(()=>{
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('团队'),
|
||||
onClick: () => navigateTo('/team/list')
|
||||
},
|
||||
{ title: teamInfo?.name || '-' }
|
||||
])
|
||||
},[state.language, teamInfo])
|
||||
|
||||
useEffect(()=>{
|
||||
getTeamInfo()
|
||||
teamId && getTeamAccessData(teamId)
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function Dashboard() {
|
||||
const { message } = App.useApp()
|
||||
const [clusters, setClusters] = useState<Array<{ id: string; name: string; enable: boolean }>>([])
|
||||
const [enabledClusters, setEnabledClusters] = useState<Array<EntityItem>>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const getClusters = () => {
|
||||
setLoading(true)
|
||||
fetchData<BasicResponse<{ clusters: Array<{ id: string; name: string; enable: boolean }> }>>(
|
||||
@@ -50,11 +50,7 @@ export default function Dashboard() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Spin
|
||||
wrapperClassName="h-full w-full pb-PAGE_INSIDE_B "
|
||||
indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
|
||||
spinning={loading}
|
||||
>
|
||||
<Spin wrapperClassName="h-full w-full pb-PAGE_INSIDE_B " indicator={<></>} spinning={loading}>
|
||||
{!loading && (
|
||||
<>
|
||||
{enabledClusters.length > 0 ? (
|
||||
|
||||
@@ -10,8 +10,8 @@ export default function DashboardList() {
|
||||
return (
|
||||
<>
|
||||
{dashboardType === 'api' && <DashboardApiList />}
|
||||
{dashboardType === 'subscriber' && <DashboardProjectList />}
|
||||
{dashboardType === 'provider' && <DashboardApplicationList />}
|
||||
{dashboardType === 'service' && <DashboardProjectList />}
|
||||
{dashboardType === 'consumer' && <DashboardApplicationList />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,89 +1,371 @@
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import MonitorTotalPage from '@dashboard/component/MonitorTotalPage'
|
||||
import { BasicResponse } from '@common/const/const'
|
||||
import {
|
||||
InvokeData,
|
||||
MessageData,
|
||||
MonitorApiData,
|
||||
MonitorSubscriberData,
|
||||
PieData,
|
||||
SearchBody
|
||||
} from '@dashboard/const/type'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import { objectToSearchParameters } from '@common/utils/router'
|
||||
import ScrollableSection from '@common/components/aoplatform/ScrollableSection'
|
||||
import { TimeRange } from '@common/components/aoplatform/TimeRangeSelector'
|
||||
import { useEffect, useState } from 'react'
|
||||
import DateSelectFilter, { TimeOption } from '@core/pages/serviceOverview/filter/DateSelectFilter'
|
||||
import { getTime } from '@dashboard/utils/dashboard'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { Card, Spin } from 'antd'
|
||||
import ServiceBarChar, { BarChartInfo } from '@core/pages/serviceOverview/charts/ServiceBarChar'
|
||||
import ServiceAreaChart from '@core/pages/serviceOverview/charts/ServiceAreaChart'
|
||||
import RankingList from '@core/pages/serviceOverview/rankingList/RankingList'
|
||||
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
|
||||
import { setBarChartInfoData } from '@core/pages/serviceOverview/utils'
|
||||
import { App } from 'antd'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
export default function DashboardTotal() {
|
||||
/** 获取数据 */
|
||||
const { fetchData } = useFetch()
|
||||
const navigateTo = useNavigate()
|
||||
const fetchPieData: (body: SearchBody) => Promise<BasicResponse<PieData>> = (body: SearchBody) =>
|
||||
fetchData<BasicResponse<PieData>>('monitor/overview/summary', {
|
||||
method: 'POST',
|
||||
eoBody: body,
|
||||
eoTransformKeys: ['request_summary', 'proxy_summary']
|
||||
})
|
||||
|
||||
const fetchInvokeData: (body: SearchBody) => Promise<BasicResponse<InvokeData>> = (body: SearchBody) =>
|
||||
fetchData<BasicResponse<InvokeData>>('monitor/overview/invoke', {
|
||||
method: 'POST',
|
||||
eoBody: body,
|
||||
eoTransformKeys: ['request_total', 'request_rate', 'proxy_total', 'proxy_rate', 'time_interval']
|
||||
})
|
||||
|
||||
const fetchMessageData: (body: SearchBody) => Promise<BasicResponse<MessageData>> = (body: SearchBody) =>
|
||||
fetchData<BasicResponse<MessageData>>('monitor/overview/message', {
|
||||
method: 'POST',
|
||||
eoBody: body,
|
||||
eoTransformKeys: ['time_interval', 'request_message', 'response_message']
|
||||
})
|
||||
|
||||
const fetchTableData: (
|
||||
body: SearchBody,
|
||||
type: 'api' | 'subscribers' | 'providers'
|
||||
) => Promise<BasicResponse<{ top10: MonitorApiData[] | MonitorSubscriberData[] }>> = (
|
||||
body: SearchBody,
|
||||
type: 'api' | 'subscribers' | 'providers'
|
||||
) =>
|
||||
fetchData<BasicResponse<{ api: MonitorApiData[]; subscribers: MonitorSubscriberData }>>('monitor/overview/top10', {
|
||||
method: 'POST',
|
||||
eoBody: { ...body, dataType: type },
|
||||
eoTransformKeys: [
|
||||
'dataType',
|
||||
'request_total',
|
||||
'request_success',
|
||||
'request_rate',
|
||||
'proxy_total',
|
||||
'proxy_success',
|
||||
'proxy_rate',
|
||||
'status_fail',
|
||||
'avg_resp',
|
||||
'max_resp',
|
||||
'min_resp',
|
||||
'avg_traffic',
|
||||
'max_traffic',
|
||||
'min_traffic',
|
||||
'min_traffic',
|
||||
'is_red'
|
||||
]
|
||||
})
|
||||
|
||||
const goToDetail: (body: SearchBody, val: MonitorApiData | MonitorSubscriberData, type: string) => void = (
|
||||
body: SearchBody,
|
||||
val: MonitorApiData | MonitorSubscriberData,
|
||||
type: string
|
||||
) => {
|
||||
// ...跳转到详情页...
|
||||
const { start: startTime, end: endTime, clusters } = body
|
||||
navigateTo(
|
||||
`/analytics/${type}/list?${objectToSearchParameters({ id: val.id, clusters: clusters || undefined, start: startTime?.toString(), end: endTime?.toString(), name: val.name }).toString()}`
|
||||
)
|
||||
/** 默认时间 */
|
||||
const [defaultTime] = useState<TimeOption>('sevenDays')
|
||||
/** 当前选中的时间范围 */
|
||||
const [timeRange, setTimeRange] = useState<TimeRange | undefined>()
|
||||
/** 当前激活的标签 */
|
||||
const [activeTab, setActiveTab] = useState('REST')
|
||||
/** 面板 loading */
|
||||
const [dashboardLoading, setDashboardLoading] = useState(false)
|
||||
/** 总数数据 */
|
||||
const [barChartInfo, setBarChartInfo] = useState<any>()
|
||||
/** 平均值数据 */
|
||||
const [perBarChartInfo, setPerBarChartInfo] = useState<any>()
|
||||
/** 排名表格数据 */
|
||||
const [topRankingList, setTopRankingList] = useState<any>([])
|
||||
/** 弹窗组件 */
|
||||
const { message } = App.useApp()
|
||||
/** 全局状态 */
|
||||
const { state } = useGlobalContext()
|
||||
/** AI 服务数据 */
|
||||
const [aiServiceOverview, setAiServiceOverview] = useState<any>()
|
||||
/** REST 服务数据 */
|
||||
const [restServiceOverview, setRestServiceOverview] = useState<any>()
|
||||
/** 时间选择回调 */
|
||||
const selectCallback = (date: TimeRange) => {
|
||||
setTimeRange(date)
|
||||
}
|
||||
|
||||
/** 获取 AI 服务信息 */
|
||||
const getAIServiceOverview = () => {
|
||||
return fetchData<BasicResponse<{ overview: any }>>('monitor/overview/chart/ai', {
|
||||
method: 'GET',
|
||||
eoParams: { start: timeRange?.start, end: timeRange?.end },
|
||||
eoTransformKeys: [
|
||||
'request_overview',
|
||||
'token_overview',
|
||||
'avg_token_overview',
|
||||
'avg_request_per_subscriber_overview',
|
||||
'avg_token_per_subscriber_overview',
|
||||
'request_total',
|
||||
'token_total',
|
||||
'avg_token',
|
||||
'min_token',
|
||||
'max_token',
|
||||
'avg_request_per_subscriber',
|
||||
'avg_token_per_subscriber',
|
||||
'input_token',
|
||||
'output_token',
|
||||
'total_token'
|
||||
]
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 存储 AI 服务数据
|
||||
setAiServiceOverview(data?.overview)
|
||||
// 设置 AI 报表数据
|
||||
setAiChartInfoData(data?.overview)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取 REST 服务信息 */
|
||||
const getRestServiceOverview = () => {
|
||||
return fetchData<BasicResponse<{ overview: any }>>('monitor/overview/chart/rest', {
|
||||
method: 'GET',
|
||||
eoParams: { start: timeRange?.start, end: timeRange?.end },
|
||||
eoTransformKeys: [
|
||||
'request_overview',
|
||||
'traffic_overview',
|
||||
'avg_request_per_subscriber_overview',
|
||||
'avg_response_time_overview',
|
||||
'avg_traffic_per_subscriber_overview',
|
||||
'request_total',
|
||||
'traffic_total',
|
||||
'avg_response_time',
|
||||
'max_response_time',
|
||||
'min_response_time',
|
||||
'avg_request_per_subscriber',
|
||||
'avg_traffic_per_subscriber'
|
||||
]
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 存储 REST 服务数据
|
||||
setRestServiceOverview(data?.overview)
|
||||
// 设置 REST 报表数据
|
||||
setRestChartInfoData(data?.overview)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 REST 服务数据
|
||||
* */
|
||||
const setRestChartInfoData = (serviceOverview: any) => {
|
||||
// 设置总数数据
|
||||
setBarChartInfo([
|
||||
// 服务请求次数
|
||||
setBarChartInfoData({
|
||||
title: $t('请求数'),
|
||||
data: serviceOverview.requestOverview,
|
||||
value: serviceOverview.requestTotal,
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
// 流量消耗总数
|
||||
setBarChartInfoData({
|
||||
title: $t('流量'),
|
||||
data: serviceOverview.trafficOverview,
|
||||
value: serviceOverview.trafficTotal,
|
||||
date: serviceOverview.date
|
||||
})
|
||||
])
|
||||
// 设置平均值数据
|
||||
setPerBarChartInfo([
|
||||
// 各个模型使用量
|
||||
{
|
||||
title: $t('平均响应时间'),
|
||||
data: serviceOverview.avgResponseTimeOverview,
|
||||
value: serviceOverview.avgResponseTime,
|
||||
date: serviceOverview.date,
|
||||
min: serviceOverview.minResponseTime,
|
||||
max: serviceOverview.maxResponseTime,
|
||||
type: 'area'
|
||||
},
|
||||
// 平均请求
|
||||
setBarChartInfoData({
|
||||
title: $t('平均请求数'),
|
||||
data: serviceOverview.avgRequestPerSubscriberOverview,
|
||||
value: serviceOverview.avgRequestPerSubscriber,
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
// 平均流量消耗
|
||||
setBarChartInfoData({
|
||||
title: $t('平均流量'),
|
||||
data: serviceOverview.avgTrafficPerSubscriberOverview,
|
||||
value: serviceOverview.avgTrafficPerSubscriber,
|
||||
date: serviceOverview.date
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 AI 服务数据
|
||||
* */
|
||||
const setAiChartInfoData = (serviceOverview: any) => {
|
||||
// 设置总数数据
|
||||
setBarChartInfo([
|
||||
// 服务请求次数
|
||||
setBarChartInfoData({
|
||||
title: $t('请求数'),
|
||||
data: serviceOverview.requestOverview,
|
||||
value: serviceOverview.requestTotal,
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
// token 消耗总数
|
||||
setBarChartInfoData({
|
||||
title: $t('Token'),
|
||||
data: serviceOverview.tokenOverview.map((item: { inputToken: number; outputToken: number }) => ({
|
||||
inputToken: item.inputToken,
|
||||
outputToken: item.outputToken
|
||||
})),
|
||||
value: serviceOverview.tokenTotal,
|
||||
date: serviceOverview.date
|
||||
})
|
||||
])
|
||||
// 设置平均值数据
|
||||
setPerBarChartInfo([
|
||||
// 平均 token 消耗
|
||||
{
|
||||
title: $t('平均 Token/s 统计'),
|
||||
data: serviceOverview.avgTokenOverview,
|
||||
value: serviceOverview.avgToken,
|
||||
date: serviceOverview.date,
|
||||
min: serviceOverview.minToken,
|
||||
max: serviceOverview.maxToken,
|
||||
type: 'area'
|
||||
},
|
||||
// 平均请求
|
||||
setBarChartInfoData({
|
||||
title: $t('平均请求数'),
|
||||
data: serviceOverview.avgRequestPerSubscriberOverview,
|
||||
value: serviceOverview.avgRequestPerSubscriber,
|
||||
date: serviceOverview.date
|
||||
}),
|
||||
// 平均 token 消耗
|
||||
setBarChartInfoData({
|
||||
title: $t('平均 Token/订阅者统计'),
|
||||
data: serviceOverview.avgTokenPerSubscriberOverview.map(
|
||||
(item: { inputToken: number; outputToken: number }) => ({
|
||||
inputToken: item.inputToken,
|
||||
outputToken: item.outputToken
|
||||
})
|
||||
),
|
||||
value: serviceOverview.avgTokenPerSubscriber,
|
||||
date: serviceOverview.date
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
/** 获取排名列表 */
|
||||
const getTopRankingList = () => {
|
||||
return fetchData<BasicResponse<{ apis: any; consumers: any }>>(
|
||||
`monitor/overview/top10/${activeTab === 'AI' ? 'ai' : 'rest'}`,
|
||||
{
|
||||
method: 'GET',
|
||||
eoParams: { start: timeRange?.start, end: timeRange?.end }
|
||||
}
|
||||
).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
// 设置排名表格数据
|
||||
setTopRankingList({
|
||||
'TOP API': data.apis,
|
||||
'TOP Consumer': data.consumers
|
||||
})
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const { startTime, endTime } = getTime(defaultTime, [])
|
||||
setTimeRange({
|
||||
start: startTime,
|
||||
end: endTime
|
||||
})
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
setDashboardLoading(true)
|
||||
try {
|
||||
const requests = []
|
||||
// 根据activeTab添加相应的请求
|
||||
if (activeTab === 'AI') {
|
||||
requests.push(getAIServiceOverview())
|
||||
} else {
|
||||
requests.push(getRestServiceOverview())
|
||||
}
|
||||
|
||||
// 添加排名列表请求
|
||||
requests.push(getTopRankingList())
|
||||
|
||||
// 等待所有请求完成
|
||||
await Promise.all(requests)
|
||||
} catch (error) {
|
||||
console.error('加载数据出错:', error)
|
||||
message.error($t('加载数据失败,请重试'))
|
||||
} finally {
|
||||
// 无论成功失败,最后都设置loading为false
|
||||
setDashboardLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (timeRange) {
|
||||
fetchData()
|
||||
}
|
||||
}, [timeRange, activeTab])
|
||||
useEffect(() => {
|
||||
if (activeTab === 'AI') {
|
||||
aiServiceOverview && setAiChartInfoData(aiServiceOverview)
|
||||
} else {
|
||||
restServiceOverview && setRestChartInfoData(restServiceOverview)
|
||||
}
|
||||
}, [state.language])
|
||||
|
||||
return (
|
||||
<MonitorTotalPage
|
||||
fetchPieData={fetchPieData}
|
||||
fetchInvokeData={fetchInvokeData}
|
||||
fetchMessageData={fetchMessageData}
|
||||
fetchTableData={fetchTableData}
|
||||
goToDetail={goToDetail}
|
||||
/>
|
||||
<div className={`h-full overflow-hidden pb-btnybase flex flex-col bg-[#fff] `}>
|
||||
<ScrollableSection>
|
||||
<div className="flex items-center flex-wrap content-before bg-MAIN_BG pr-PAGE_INSIDE_X ">
|
||||
<div className="pt-btnybase mr-[10px]">{$t('服务')}:</div>
|
||||
<div className="mt-3 tab-nav flex rounded-md overflow-hidden border border-solid border-[#3D46F2] w-[150px] mr-[30px]">
|
||||
<div
|
||||
className={`tab-item text-center px-5 py-1.5 cursor-pointer w-[50%] text-sm transition-colors ${activeTab === 'REST' ? 'bg-[#3D46F2] text-white' : 'bg-white text-[#3D46F2]'}`}
|
||||
onClick={() => setActiveTab('REST')}
|
||||
>
|
||||
REST
|
||||
</div>
|
||||
<div
|
||||
className={`tab-item text-center px-5 py-1.5 cursor-pointer w-[50%] text-sm transition-colors ${activeTab === 'AI' ? 'bg-[#3D46F2] text-white' : 'bg-white text-[#3D46F2]'}`}
|
||||
onClick={() => setActiveTab('AI')}
|
||||
>
|
||||
AI
|
||||
</div>
|
||||
</div>
|
||||
<DateSelectFilter selectCallback={selectCallback} customClassNames="pt-[12px]" defaultTime={defaultTime} />
|
||||
</div>
|
||||
<Spin
|
||||
className="h-full pb-[20px]"
|
||||
wrapperClassName={`flex-1 overflow-auto`}
|
||||
indicator={
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ transform: 'scale(1.5)' }}>
|
||||
<LoadingOutlined style={{ fontSize: 30 }} spin />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
spinning={dashboardLoading}
|
||||
>
|
||||
<div className="mr-[40px]">
|
||||
<div className="mt-[20px] flex mb-[10px]">
|
||||
{barChartInfo?.map((item: BarChartInfo, index: number) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 min-w-[430px] rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'py-[15px] px-[0px]'
|
||||
}}
|
||||
>
|
||||
<ServiceBarChar key={index} height={400} dataInfo={item} customClassNames="flex-1"></ServiceBarChar>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex mb-[10px]">
|
||||
{perBarChartInfo?.map((item: any, index: number) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 min-w-[284px] rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'py-[15px] px-[0px]'
|
||||
}}
|
||||
>
|
||||
{item.type === 'area' ? (
|
||||
<>
|
||||
<ServiceAreaChart
|
||||
key={index}
|
||||
height={270}
|
||||
dataInfo={item}
|
||||
customClassNames="flex-1 relative"
|
||||
></ServiceAreaChart>
|
||||
</>
|
||||
) : (
|
||||
<ServiceBarChar key={index} height={270} dataInfo={item} customClassNames="flex-1"></ServiceBarChar>
|
||||
)}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<RankingList
|
||||
topRankingList={topRankingList}
|
||||
serviceType={activeTab === 'AI' ? 'aiService' : 'restService'}
|
||||
/>
|
||||
</div>
|
||||
</Spin>
|
||||
</ScrollableSection>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ const PUBLIC_ROUTES: RouteConfig[] = [
|
||||
key: uuidv4(),
|
||||
children: [
|
||||
{
|
||||
path: 'serviceHub',
|
||||
path: 'portal',
|
||||
component: <Outlet />,
|
||||
key: uuidv4(),
|
||||
children: [
|
||||
@@ -151,7 +151,7 @@ const PUBLIC_ROUTES: RouteConfig[] = [
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
component: <Navigate to={'/serviceHub/list'} replace />,
|
||||
component: <Navigate to={'/portal/list'} replace />,
|
||||
key: uuidv4()
|
||||
}
|
||||
]
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Tool } from '@modelcontextprotocol/sdk/types.js'
|
||||
import McpToolsContainer from '@core/pages/mcpService/McpToolsContainer.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import TopBreadcrumb from '@common/components/aoplatform/Breadcrumb.tsx'
|
||||
import ServiceInfoCard from '@common/components/aoplatform/serviceInfoCard.tsx'
|
||||
|
||||
type TabItemType = {
|
||||
key: string
|
||||
@@ -32,18 +33,12 @@ const ServiceHubDetail = () => {
|
||||
const { serviceId } = useParams<RouterParams>()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [serviceBasicInfo, setServiceBasicInfo] = useState<ServiceBasicInfoType>()
|
||||
const [serviceName, setServiceName] = useState<string>()
|
||||
const [serviceDesc, setServiceDesc] = useState<string>()
|
||||
const [serviceDoc, setServiceDoc] = useState<string>()
|
||||
const { fetchData } = useFetch()
|
||||
const applyRef = useRef<ApplyServiceHandle>(null)
|
||||
const { modal, message } = App.useApp()
|
||||
const [mySystemOptionList, setMySystemOptionList] = useState<DefaultOptionType[]>()
|
||||
const [service, setService] = useState<ServiceDetailType>()
|
||||
const [serviceMetrics, setServiceMetrics] = useState<{ title: string; icon: React.ReactNode; value: string }[]>([])
|
||||
const [serviceTags, setServiceTags] = useState<
|
||||
{ color: string; textColor: string; title: string; content: React.ReactNode }[]
|
||||
>([])
|
||||
const [tools, setTools] = useState<Tool[]>([])
|
||||
const [tabItem, setTabItem] = useState<TabItemType[]>([])
|
||||
const [currentTab, setCurrentTab] = useState('')
|
||||
@@ -149,10 +144,7 @@ servers:
|
||||
apiDoc: modifyApiDoc(data.service.apiDoc, data.service.basic?.invokeAddress)
|
||||
})
|
||||
setServiceBasicInfo(data.service.basic)
|
||||
setServiceName(data.service.name)
|
||||
setServiceDesc(data.service.description)
|
||||
setServiceDoc(DOMPurify.sanitize(data.service.document))
|
||||
setServiceMetricsList(data.service.basic)
|
||||
setTabItemList(data.service.basic)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
@@ -164,54 +156,6 @@ servers:
|
||||
setCurrentTab(value)
|
||||
}
|
||||
|
||||
const setServiceMetricsList = (serviceBasicInfo: ServiceBasicInfoType) => {
|
||||
// 设置服务指标数据
|
||||
setServiceMetrics([
|
||||
{
|
||||
title: 'API 数量',
|
||||
icon: <ApiOutlined className="mr-[1px] text-[14px] h-[14px] w-[14px]" />,
|
||||
value: serviceBasicInfo.apiNum.toString()
|
||||
},
|
||||
{
|
||||
title: '接入消费者数量',
|
||||
icon: <Icon icon="tabler:api-app" width="14" height="14" />,
|
||||
value: serviceBasicInfo.appNum.toString()
|
||||
},
|
||||
{
|
||||
title: '30天内调用次数',
|
||||
icon: <Icon icon="iconoir:graph-up" width="14" height="14" />,
|
||||
value: formatInvokeCount(serviceBasicInfo.invokeCount ?? 0)
|
||||
}
|
||||
])
|
||||
// 设置服务标签数据
|
||||
const tags = [
|
||||
{
|
||||
color: '#7371fc1b',
|
||||
textColor: 'text-theme',
|
||||
title: serviceBasicInfo?.catalogue?.name || '-',
|
||||
content: serviceBasicInfo?.catalogue?.name || '-'
|
||||
},
|
||||
{
|
||||
color: `#${serviceBasicInfo?.serviceKind === 'ai' ? 'EADEFF' : 'DEFFE7'}`,
|
||||
textColor: 'text-[#000]',
|
||||
title: serviceBasicInfo?.serviceKind || '-',
|
||||
content: SERVICE_KIND_OPTIONS.find((x) => x.value === serviceBasicInfo?.serviceKind)?.label || '-'
|
||||
}
|
||||
]
|
||||
|
||||
// 如果启用了MCP,添加MCP标签
|
||||
if (serviceBasicInfo?.enableMcp) {
|
||||
tags.push({
|
||||
color: '#FFF0C1',
|
||||
textColor: 'text-[#000]',
|
||||
title: 'MCP',
|
||||
content: 'MCP'
|
||||
})
|
||||
}
|
||||
|
||||
setServiceTags(tags)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!serviceId) {
|
||||
console.warn('缺少serviceId')
|
||||
@@ -227,11 +171,12 @@ servers:
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('API 门户'),
|
||||
onClick: () => navigate(`/serviceHub/list`)
|
||||
onClick: () => navigate(`/portal/list`)
|
||||
},
|
||||
{ title: service?.name || '-' },
|
||||
{ title: $t('服务详情') }
|
||||
])
|
||||
}, [state.language])
|
||||
}, [state.language, service])
|
||||
|
||||
const getMySelectList = () => {
|
||||
setMySystemOptionList([])
|
||||
@@ -270,7 +215,7 @@ servers:
|
||||
content: (
|
||||
<ApplyServiceModal
|
||||
ref={applyRef}
|
||||
entity={{ ...serviceBasicInfo!, name: serviceName!, id: serviceId! }}
|
||||
entity={{ ...serviceBasicInfo!, name: service?.name || '', id: serviceId! }}
|
||||
mySystemOptionList={mySystemOptionList!}
|
||||
/>
|
||||
),
|
||||
@@ -293,19 +238,6 @@ servers:
|
||||
const handleToolsChange = (value: Tool[]) => {
|
||||
setTools(value)
|
||||
}
|
||||
// 格式化调用次数,添加K和M单位
|
||||
const formatInvokeCount = (count: number | null | undefined): string => {
|
||||
if (count === null || count === undefined) return '-'
|
||||
if (count >= 1000000) {
|
||||
const value = Math.floor(count / 100000) / 10
|
||||
return `${value}M`
|
||||
}
|
||||
if (count >= 1000) {
|
||||
const value = Math.floor(count / 100) / 10
|
||||
return `${value}K`
|
||||
}
|
||||
return count.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义一个更新标签项的函数,在serviceBasicInfo或tools变化时调用
|
||||
@@ -429,76 +361,23 @@ servers:
|
||||
return (
|
||||
<div className="pr-[40px]">
|
||||
<header>
|
||||
<TopBreadcrumb handleBackCallback={() => navigate(`/serviceHub/list`)} />
|
||||
<TopBreadcrumb handleBackCallback={() => navigate(`/portal/list`)} />
|
||||
</header>
|
||||
<Card
|
||||
style={{
|
||||
borderRadius: '10px',
|
||||
background: 'linear-gradient(35deg, rgb(246, 246, 260) 0%, rgb(255, 255, 255) 40%)'
|
||||
<ServiceInfoCard
|
||||
serviceBasicInfo={{
|
||||
...serviceBasicInfo,
|
||||
serviceName: service?.name || '',
|
||||
serviceDesc: service?.description || ''
|
||||
}}
|
||||
className={`w-full mt-[20px]`}
|
||||
classNames={{
|
||||
body: 'p-[15px] h-[180px]'
|
||||
}}
|
||||
>
|
||||
<div className="service-info">
|
||||
<div className="flex items-center">
|
||||
<div>
|
||||
<Avatar
|
||||
shape="square"
|
||||
size={50}
|
||||
className={`rounded-[12px] border-none rounded-[12px] ${serviceBasicInfo?.logo ? 'bg-[linear-gradient(135deg,white,#f0f0f0)]' : 'bg-theme'}`}
|
||||
src={
|
||||
serviceBasicInfo?.logo ? (
|
||||
<img
|
||||
src={serviceBasicInfo?.logo}
|
||||
alt="Logo"
|
||||
style={{ maxWidth: '200px', width: '45px', height: '45px', objectFit: 'unset' }}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
icon={serviceBasicInfo?.logo ? '' : <Icon icon="tabler:api-app" />}
|
||||
>
|
||||
{' '}
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="pl-[20px] w-[calc(100%-50px)] overflow-hidden">
|
||||
<p className="text-[14px] h-[20px] leading-[20px] truncate font-bold w-full flex items-center gap-[4px]">
|
||||
{serviceName}
|
||||
</p>
|
||||
<div className="mt-[5px] h-[20px] flex items-center font-normal">
|
||||
{serviceTags.map((tag, index) => (
|
||||
<Tag
|
||||
key={index}
|
||||
color={tag.color}
|
||||
className={`${tag.textColor} font-normal border-0 mr-[12px] max-w-[150px] truncate`}
|
||||
bordered={false}
|
||||
title={tag.title}
|
||||
>
|
||||
{tag.content}
|
||||
</Tag>
|
||||
))}
|
||||
{serviceMetrics.map((item, index) => (
|
||||
<Tooltip key={index} title={$t(item.title)}>
|
||||
<span className="mr-[12px] flex items-center">
|
||||
<span className="h-[14px] mr-[4px] flex items-center">{item.icon}</span>
|
||||
<span className="font-normal text-[14px]">{item.value}</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className="line-clamp-2 mt-[15px] text-[12px] text-[#666]" title={serviceDesc}>
|
||||
{serviceDesc || $t('暂无服务描述')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="absolute bottom-[15px]">
|
||||
<Button type="primary" onClick={() => openModal('apply')}>
|
||||
{$t('申请')}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
customClassName="mt-[20px]"
|
||||
actionSlot={
|
||||
<>
|
||||
<Button type="primary" onClick={() => openModal('apply')}>
|
||||
{$t('申请')}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<div className="flex">
|
||||
<Tabs
|
||||
className="p-btnbase pr-0 overflow-hidden [&>.ant-tabs-content-holder]:overflow-auto w-full flex-1 mr-[10px]"
|
||||
|
||||
+1
-1
@@ -254,7 +254,7 @@ export default function ManagementInsideService() {
|
||||
<Button
|
||||
type="text"
|
||||
className="bg-[#7371fc20] hover:bg-[#7371fc19] text-theme"
|
||||
onClick={() => window.open(`/serviceHub/detail/${item.service.id}`, '_blank')}
|
||||
onClick={() => window.open(`/portal/detail/${item.service.id}`, '_blank')}
|
||||
>
|
||||
{$t('API 文档')}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user