mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-04 10:13:53 +08:00
Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability See merge request apipark/APIPark!345
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -187,6 +187,7 @@ export const TranslateWord = () => {
|
||||
{$t('调用地址')}
|
||||
{$t('消费者 IP')}
|
||||
{$t('鉴权名称')}
|
||||
{$t('日志输出')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ const ServiceInfoCard = ({
|
||||
* 打开服务详情页面
|
||||
*/
|
||||
const openInPortal = () => {
|
||||
window.open(`/serviceHub/detail/${serviceOverview?.id}`, '_blank')
|
||||
window.open(`/portal/detail/${serviceOverview?.id}`, '_blank')
|
||||
}
|
||||
|
||||
// 格式化调用次数,添加K和M单位
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"Kb58e0c3f": "Service",
|
||||
"Kc9e489f5": "Team",
|
||||
"K61c89f5f": "API Portal",
|
||||
"K16d71239": "Analysis",
|
||||
"K16d71239": "Analytics",
|
||||
"K714c192d": "Call Statistics",
|
||||
"Kd57dfe97": "Topology",
|
||||
"K3fe97dcc": "System Settings",
|
||||
|
||||
@@ -535,7 +535,7 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
|
||||
],
|
||||
|
||||
[
|
||||
'serviceHub',
|
||||
'portal',
|
||||
{
|
||||
type: 'module',
|
||||
component: <Outlet />,
|
||||
@@ -702,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 }>[] = [
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -9,6 +9,7 @@ type AreaChartInfo = {
|
||||
data: number[]
|
||||
max: string
|
||||
min: string
|
||||
showXAxis?: boolean
|
||||
}
|
||||
|
||||
type ServiceAreaCharProps = {
|
||||
@@ -24,27 +25,65 @@ const ServiceAreaChart = ({ customClassNames, dataInfo, height }: ServiceAreaCha
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
position: function (pt) {
|
||||
return [pt[0], '10%']
|
||||
formatter: function (value: any) {
|
||||
// 如果是数组,取第一个参数的name
|
||||
const param = Array.isArray(value) ? value[0] : value
|
||||
console.log('params==', param)
|
||||
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
|
||||
}
|
||||
},
|
||||
title: {
|
||||
show: false
|
||||
},
|
||||
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: '5%',
|
||||
left: '3%',
|
||||
right: '3%',
|
||||
bottom: '5%',
|
||||
top: '100px',
|
||||
bottom: '0%',
|
||||
top: '110px',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: dataInfo.date
|
||||
data: dataInfo.date,
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#ccc'
|
||||
}
|
||||
},
|
||||
show: false
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
@@ -59,7 +98,51 @@ const ServiceAreaChart = ({ customClassNames, dataInfo, height }: ServiceAreaCha
|
||||
show: false
|
||||
}
|
||||
},
|
||||
dataZoom: [],
|
||||
// 添加数据缩放组件,实现鼠标放大缩小,后续可能需要
|
||||
// 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: !dataInfo.data.length
|
||||
? [
|
||||
{
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'middle',
|
||||
style: {
|
||||
text: $t('暂无数据'),
|
||||
fontSize: 14,
|
||||
fill: '#999'
|
||||
}
|
||||
}
|
||||
]
|
||||
: [],
|
||||
series: [
|
||||
{
|
||||
name: dataInfo.title,
|
||||
@@ -94,23 +177,30 @@ const ServiceAreaChart = ({ customClassNames, dataInfo, height }: ServiceAreaCha
|
||||
}
|
||||
setOption(option)
|
||||
}
|
||||
// 使用深度监听来确保图表数据更新
|
||||
useEffect(() => {
|
||||
if (!dataInfo) return
|
||||
setChartOption(dataInfo)
|
||||
}, [dataInfo])
|
||||
|
||||
// 直接获取 ECharts 实例并设置选项
|
||||
const echartsInstance = chartRef.current?.getEchartsInstance()
|
||||
if (echartsInstance) {
|
||||
// 清除已有的图表
|
||||
echartsInstance.clear()
|
||||
// 重新设置选项
|
||||
setChartOption(dataInfo)
|
||||
}
|
||||
}, [dataInfo, JSON.stringify(dataInfo)])
|
||||
return (
|
||||
<div className={`w-full ${customClassNames}`}>
|
||||
<div className="absolute top-[10px] left-[10px] w-full">
|
||||
<div className="text-[16px] text-[#999]">{$t(dataInfo?.title || '')}</div>
|
||||
<div className="relative top-[-6px]">
|
||||
<span className="text-[30px] font-bold">{dataInfo?.value}</span>
|
||||
<div className="absolute top-[5px] right-[8%] grid grid-cols-[auto_auto] gap-y-1 justify-items-end">
|
||||
<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-[#ff4683] text-[9px]">▲</span>
|
||||
<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-[#4bdb6a] text-[9px]">▼</span>
|
||||
<span className="text-[#27B148] text-[9px]">▼</span>
|
||||
</div>
|
||||
<span className="ml-1 text-right">{dataInfo?.min}</span>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ export type BarChartInfo = {
|
||||
color: string
|
||||
value: number[]
|
||||
}[]
|
||||
showXAxis?: boolean
|
||||
}
|
||||
|
||||
type ServiceBarCharProps = {
|
||||
@@ -20,7 +21,6 @@ type ServiceBarCharProps = {
|
||||
height?: number
|
||||
}
|
||||
|
||||
|
||||
const ServiceBarChar = ({ customClassNames, dataInfo, height }: ServiceBarCharProps) => {
|
||||
const chartRef = useRef<ECharts>(null)
|
||||
const [option, setOption] = useState<EChartsOption | undefined>({})
|
||||
@@ -33,6 +33,7 @@ const ServiceBarChar = ({ customClassNames, dataInfo, height }: ServiceBarCharPr
|
||||
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>`
|
||||
@@ -63,8 +64,8 @@ const ServiceBarChar = ({ customClassNames, dataInfo, height }: ServiceBarCharPr
|
||||
const option: EChartsOption = {
|
||||
title: [
|
||||
{
|
||||
text: '{titleStyle|' + $t(dataInfo.title) + '}\n{valueStyle|' + dataInfo.value + '}',
|
||||
left: '4%',
|
||||
text: '{titleStyle|' + $t(dataInfo.title) + '}\n\n{valueStyle|' + dataInfo.value + '}',
|
||||
left: '2%',
|
||||
top: '0',
|
||||
textStyle: {
|
||||
rich: {
|
||||
@@ -75,8 +76,8 @@ const ServiceBarChar = ({ customClassNames, dataInfo, height }: ServiceBarCharPr
|
||||
lineHeight: 20
|
||||
},
|
||||
valueStyle: {
|
||||
fontSize: 25,
|
||||
color: '#000',
|
||||
fontSize: 32,
|
||||
color: '#101010',
|
||||
fontWeight: 500,
|
||||
lineHeight: 40
|
||||
}
|
||||
@@ -85,10 +86,10 @@ const ServiceBarChar = ({ customClassNames, dataInfo, height }: ServiceBarCharPr
|
||||
}
|
||||
],
|
||||
grid: {
|
||||
left: '5%',
|
||||
left: '3%',
|
||||
right: '3%',
|
||||
bottom: '5%',
|
||||
top: '100px',
|
||||
bottom: '0%',
|
||||
top: '110px',
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: {
|
||||
@@ -124,12 +125,14 @@ const ServiceBarChar = ({ customClassNames, dataInfo, height }: ServiceBarCharPr
|
||||
lineStyle: {
|
||||
color: '#ccc'
|
||||
}
|
||||
}
|
||||
},
|
||||
show: false
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '',
|
||||
min: 0,
|
||||
minInterval: 1,
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
@@ -140,6 +143,50 @@ const ServiceBarChar = ({ customClassNames, dataInfo, height }: ServiceBarCharPr
|
||||
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
|
||||
? [
|
||||
{
|
||||
@@ -171,10 +218,19 @@ const ServiceBarChar = ({ customClassNames, dataInfo, height }: ServiceBarCharPr
|
||||
setOption(option)
|
||||
}
|
||||
|
||||
// 使用深度监听来确保图表数据更新
|
||||
useEffect(() => {
|
||||
if (!dataInfo) return
|
||||
setChartOption(dataInfo)
|
||||
}, [dataInfo])
|
||||
|
||||
// 直接获取 ECharts 实例并设置选项
|
||||
const echartsInstance = chartRef.current?.getEchartsInstance()
|
||||
if (echartsInstance) {
|
||||
// 清除已有的图表
|
||||
echartsInstance.clear()
|
||||
// 重新设置选项
|
||||
setChartOption(dataInfo)
|
||||
}
|
||||
}, [dataInfo, JSON.stringify(dataInfo)])
|
||||
return (
|
||||
<div className={`w-full ${customClassNames}`}>
|
||||
<ECharts ref={chartRef} option={option} style={{ height: height || 400 }} opts={{ renderer: 'svg' }} />
|
||||
|
||||
@@ -18,33 +18,30 @@ const Indicator = ({ indicatorInfo }: { indicatorInfo: any }) => {
|
||||
|
||||
/** 设置服务指标 */
|
||||
const setIndicatorList = () => {
|
||||
const side = indicatorInfo?.serviceKind === 'ai' ? 'aiInside' : 'inside'
|
||||
setIndicator([
|
||||
{
|
||||
title: indicatorInfo?.enableMcp ? 'APIs / Tools' : 'APIs',
|
||||
link: `/serviceHub/detail/${indicatorInfo?.serviceId}`,
|
||||
link: `/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/route`,
|
||||
content: indicatorInfo?.apiNum ?? 0
|
||||
},
|
||||
{
|
||||
title: $t('订阅数量'),
|
||||
link: `/consumer/list/${indicatorInfo?.teamId}`,
|
||||
link: `/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/subscriber`,
|
||||
content: indicatorInfo?.subscriberNum ?? 0
|
||||
},
|
||||
{
|
||||
title: 'MCP',
|
||||
link: `/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/setting`,
|
||||
content: (
|
||||
<>
|
||||
{/* green */}
|
||||
<Button
|
||||
color={indicatorInfo?.enableMcp ? 'green' : 'primary'}
|
||||
className="w-full rounded-[10px]"
|
||||
variant="outlined"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (indicatorInfo?.enableMcp) {
|
||||
window.open(`/serviceHub/detail/${indicatorInfo?.serviceId}`, '_blank')
|
||||
} else {
|
||||
navigateTo(`/service/${indicatorInfo?.teamId}/aiInside/${indicatorInfo?.serviceId}/setting`)
|
||||
}
|
||||
navigateTo(`/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/setting`)
|
||||
}}
|
||||
>
|
||||
{indicatorInfo?.enableMcp ? $t('已开启') : $t('开启 MCP')}
|
||||
@@ -65,19 +62,23 @@ const Indicator = ({ indicatorInfo }: { indicatorInfo: any }) => {
|
||||
{indicatorList.map((item, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 cursor-pointer shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] transition duration-500 hover:shadow-[0_5px_20px_0_rgba(0,0,0,0.15)] hover:scale-[1.02] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
className={`flex-1 cursor-pointer rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'p-[15px]'
|
||||
body: 'py-[20px] px-[18px]'
|
||||
}}
|
||||
onClick={() => {
|
||||
window.open(item.link)
|
||||
if (item.link) {
|
||||
navigateTo(item.link)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="text-[14px] font-semibold text-gray-400 mb-[15px]">
|
||||
<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-[40px] font-semibold' : 'block mt-[30px]'}`}>{item.content}</div>
|
||||
<div className={`${index < 2 ? 'text-[32px] font-medium text-[#101010]' : 'block mt-[30px]'}`}>
|
||||
{item.content}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -65,7 +65,7 @@ const RankingList = ({ topRankingList, serviceType }: { topRankingList: RankingL
|
||||
key={index}
|
||||
className={`flex-1 h-fit rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'p-[15px] pb-[0px]'
|
||||
body: 'p-[15px]'
|
||||
}}
|
||||
>
|
||||
<div className="mb-[10px]">
|
||||
|
||||
@@ -99,6 +99,7 @@ const ServiceOverview = ({ serviceType }: { serviceType: 'aiService' | 'restServ
|
||||
subscriberNum: serviceOverview.subscriberNum,
|
||||
teamId: teamId,
|
||||
enableMcp: serviceOverview.enableMcp,
|
||||
serviceKind: serviceOverview.serviceKind,
|
||||
serviceId: serviceId
|
||||
})
|
||||
// 设置总数数据
|
||||
@@ -128,21 +129,24 @@ const ServiceOverview = ({ serviceType }: { serviceType: 'aiService' | 'restServ
|
||||
date: serviceOverview.date,
|
||||
max: serviceOverview.maxResponseTime,
|
||||
min: serviceOverview.minResponseTime,
|
||||
type: 'area'
|
||||
type: 'area',
|
||||
showXAxis: false
|
||||
},
|
||||
// 平均请求
|
||||
setBarChartInfoData({
|
||||
title: $t('平均请求数'),
|
||||
data: serviceOverview.avgRequestPerSubscriberOverview,
|
||||
value: serviceOverview.avgRequestPerSubscriber,
|
||||
date: serviceOverview.date
|
||||
date: serviceOverview.date,
|
||||
showXAxis: false
|
||||
}),
|
||||
// 平均流量消耗
|
||||
setBarChartInfoData({
|
||||
title: $t('平均流量'),
|
||||
data: serviceOverview.avgTrafficPerSubscriberOverview,
|
||||
value: serviceOverview.avgTrafficPerSubscriber,
|
||||
date: serviceOverview.date
|
||||
date: serviceOverview.date,
|
||||
showXAxis: false
|
||||
})
|
||||
])
|
||||
}
|
||||
@@ -157,6 +161,7 @@ const ServiceOverview = ({ serviceType }: { serviceType: 'aiService' | 'restServ
|
||||
subscriberNum: serviceOverview.subscriberNum,
|
||||
teamId: teamId,
|
||||
enableMcp: serviceOverview.enableMcp,
|
||||
serviceKind: serviceOverview.serviceKind,
|
||||
serviceId: serviceId
|
||||
})
|
||||
// 设置总数数据
|
||||
@@ -314,9 +319,9 @@ const ServiceOverview = ({ serviceType }: { serviceType: 'aiService' | 'restServ
|
||||
{barChartInfo?.map((item: BarChartInfo, index: number) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 cursor-pointer rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
className={`flex-1 rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'p-[15px]'
|
||||
body: 'py-[15px] px-[0px]'
|
||||
}}
|
||||
>
|
||||
<ServiceBarChar key={index} height={400} dataInfo={item} customClassNames="flex-1"></ServiceBarChar>
|
||||
@@ -327,22 +332,22 @@ const ServiceOverview = ({ serviceType }: { serviceType: 'aiService' | 'restServ
|
||||
{perBarChartInfo?.map((item: any, index: number) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 cursor-pointer rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
className={`flex-1 rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'p-[15px]'
|
||||
body: 'py-[15px] px-[0px]'
|
||||
}}
|
||||
>
|
||||
{item.type === 'area' ? (
|
||||
<>
|
||||
<ServiceAreaChart
|
||||
key={index}
|
||||
height={250}
|
||||
height={270}
|
||||
dataInfo={item}
|
||||
customClassNames="flex-1 relative"
|
||||
></ServiceAreaChart>
|
||||
</>
|
||||
) : (
|
||||
<ServiceBarChar key={index} height={250} dataInfo={item} customClassNames="flex-1"></ServiceBarChar>
|
||||
<ServiceBarChar key={index} height={270} dataInfo={item} customClassNames="flex-1"></ServiceBarChar>
|
||||
)}
|
||||
</Card>
|
||||
))}
|
||||
|
||||
@@ -3,15 +3,17 @@ export type BarData = {
|
||||
value: string
|
||||
date: string[]
|
||||
data: any[]
|
||||
showXAxis?: boolean
|
||||
}
|
||||
export const setBarChartInfoData = ({ title, value, data, date }: BarData) => {
|
||||
export const setBarChartInfoData = ({ title, value, data, date, showXAxis }: BarData) => {
|
||||
// 首先获取所有的键名(假设所有对象的键名都一样)
|
||||
if (data.length === 0) {
|
||||
return {
|
||||
title,
|
||||
value,
|
||||
date,
|
||||
data: []
|
||||
data: [],
|
||||
showXAxis: !!showXAxis
|
||||
}
|
||||
}
|
||||
if (typeof data[0] !== 'object') {
|
||||
@@ -19,7 +21,8 @@ export const setBarChartInfoData = ({ title, value, data, date }: BarData) => {
|
||||
title,
|
||||
value,
|
||||
date,
|
||||
data
|
||||
data,
|
||||
showXAxis: !!showXAxis
|
||||
}
|
||||
}
|
||||
// 从第一个对象中获取所有键名
|
||||
@@ -56,6 +59,7 @@ export const setBarChartInfoData = ({ title, value, data, date }: BarData) => {
|
||||
title,
|
||||
value,
|
||||
date,
|
||||
data: transformedData
|
||||
data: transformedData,
|
||||
showXAxis: !!showXAxis
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ export default function DashboardList() {
|
||||
return (
|
||||
<>
|
||||
{dashboardType === 'api' && <DashboardApiList />}
|
||||
{dashboardType === 'subscriber' && <DashboardProjectList />}
|
||||
{dashboardType === 'provider' && <DashboardApplicationList />}
|
||||
{dashboardType === 'service' && <DashboardProjectList />}
|
||||
{dashboardType === 'consumer' && <DashboardApplicationList />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -326,9 +326,9 @@ export default function DashboardTotal() {
|
||||
{barChartInfo?.map((item: BarChartInfo, index: number) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 cursor-pointer rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
className={`flex-1 rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'p-[15px]'
|
||||
body: 'py-[15px] px-[0px]'
|
||||
}}
|
||||
>
|
||||
<ServiceBarChar key={index} height={400} dataInfo={item} customClassNames="flex-1"></ServiceBarChar>
|
||||
@@ -339,22 +339,22 @@ export default function DashboardTotal() {
|
||||
{perBarChartInfo?.map((item: any, index: number) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`flex-1 cursor-pointer rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
className={`flex-1 rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
|
||||
classNames={{
|
||||
body: 'p-[15px]'
|
||||
body: 'py-[15px] px-[0px]'
|
||||
}}
|
||||
>
|
||||
{item.type === 'area' ? (
|
||||
<>
|
||||
<ServiceAreaChart
|
||||
key={index}
|
||||
height={250}
|
||||
height={270}
|
||||
dataInfo={item}
|
||||
customClassNames="flex-1 relative"
|
||||
></ServiceAreaChart>
|
||||
</>
|
||||
) : (
|
||||
<ServiceBarChar key={index} height={250} dataInfo={item} customClassNames="flex-1"></ServiceBarChar>
|
||||
<ServiceBarChar key={index} height={270} dataInfo={item} customClassNames="flex-1"></ServiceBarChar>
|
||||
)}
|
||||
</Card>
|
||||
))}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
]
|
||||
|
||||
@@ -171,7 +171,7 @@ servers:
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('API 门户'),
|
||||
onClick: () => navigate(`/serviceHub/list`)
|
||||
onClick: () => navigate(`/portal/list`)
|
||||
},
|
||||
{ title: service?.name || '-' },
|
||||
{ title: $t('服务详情') }
|
||||
@@ -361,7 +361,7 @@ servers:
|
||||
return (
|
||||
<div className="pr-[40px]">
|
||||
<header>
|
||||
<TopBreadcrumb handleBackCallback={() => navigate(`/serviceHub/list`)} />
|
||||
<TopBreadcrumb handleBackCallback={() => navigate(`/portal/list`)} />
|
||||
</header>
|
||||
<ServiceInfoCard
|
||||
serviceBasicInfo={{
|
||||
|
||||
+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