feat: apo tool test run shpw data display

pull/17608/head
keting lu 1 year ago
parent 7dd1ee3691
commit 49680f909e

@ -2,62 +2,99 @@ import React, { memo, useEffect, useRef, useState } from 'react'
import ReactECharts from 'echarts-for-react' import ReactECharts from 'echarts-for-react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import dayjs from 'dayjs' import dayjs from 'dayjs'
const formatMGT = (y) => {
const yy = Math.abs(y)
if (yy >= 1024 * 1024) {
return y < 0
? `${-1 * +(yy / (1024 * 1024)).toFixed(2)}T`
: `${(yy / (1024 * 1024)).toFixed(2)}T`
}
else if (yy >= 1024) {
return y < 0 ? `${-1 * +(yy / 1024).toFixed(2)}G` : `${(yy / 1024).toFixed(2)}G`
}
else if (yy < 1024 && yy >= 1) {
return y < 0 ? `${-1 * +yy.toFixed(2)}M` : `${yy.toFixed(2)}M`
}
else if (yy < 1 && yy > 0) {
return y < 0 ? `${-1 * yy}M` : `${yy}M`
}
else if (yy === 0) {
return 0
}
else {
return `${yy}M`
}
}
export const adjustAlpha = (color: string, alpha: number) => { export const adjustAlpha = (color: string, alpha: number) => {
const rgba = color.match(/\d+/g) const rgba = color.match(/\d+/g)
return `rgba(${rgba[0]}, ${rgba[1]}, ${rgba[2]}, ${alpha})` return `rgba(${rgba[0]}, ${rgba[1]}, ${rgba[2]}, ${alpha})`
} }
const formatTimeUTC = (value: number) => { const formatTimeUTC = (value: number) => {
return dayjs(value * 1000).format('HH:mm:ss') return dayjs(value).format('HH:mm:ss')
} }
const formatDay = (value: number) => { const formatDay = (value: number) => {
return dayjs(value).format('YYYY-MM-DD HH:mm:ss') return dayjs(value).format('YYYY-MM-DD HH:mm:ss')
} }
type LineChartData = { type ChartData = {
[key: string]: number; [key: string]: number;
} }
type LineChartType = 'cpu' | 'network' | 'memory' type LineChartData = {
type LineChartProps = { legend: string
data: LineChartData legendFormat: string
type: LineChartType labels: {
} [key: string]: string;
const DelayLineChartTitleMap: Record<LineChartType, string> = { }
cpu: 'cpu指标', chart: {
network: '网络时延', chartData: ChartData
memory: '内存占用', }
} }
const MetricsLineChartColor = { type LineChartProps = {
network: 'rgba(212, 164, 235, 1)', data: LineChartData[]
memory: 'rgba(212, 164, 235, 1)', unit: string
cpu: 'rgba(55, 162,235, 1)',
} }
// const DelayLineChartTitleMap: Record<LineChartType, string> = {
// cpu: 'cpu指标',
// network: '网络时延',
// memory: '内存占用',
// }
// const MetricsLineChartColor = {
// network: 'rgba(212, 164, 235, 1)',
// memory: 'rgba(212, 164, 235, 1)',
// cpu: 'rgba(55, 162,235, 1)',
// }
export const YValueMinInterval = { export const YValueMinInterval = {
network: 0.01, network: 0.01,
memory: 0.01, memory: 0.01,
cpu: 0.01, cpu: 0.01,
} }
const LineChart = ({ type, data }: LineChartProps) => { const LineChart = (props: LineChartProps) => {
const { data, unit } = props
const { t } = useTranslation() const { t } = useTranslation()
const chartRef = useRef(null) const chartRef = useRef(null)
const convertYValue = (value: number) => { const convertYValue = (value: number) => {
switch (type) { switch (unit) {
case 'network': // case 'network':
if (value > 0 && value < 0.01) // if (value > 0 && value < 0.01)
return '< 0.01 s' // return '< 0.01 s'
return `${value}s` // return `${value}s`
case 'memory': // case 'memory':
// if (value > 0 && value < 0.01)
// return '< 0.01 bytes'
// return `${value}bytes`
case 'percent':
if (value > 0 && value < 0.01) if (value > 0 && value < 0.01)
return '< 0.01 bytes'
return `${value}bytes`
case 'cpu':
if (value > 0 && value < 0.0001)
return '< 0.01%' return '< 0.01%'
return `${Number.parseFloat((value * 100).toFixed(2))}%` return `${Number.parseFloat((value).toFixed(2))}%`
// case 'tps': case 'bytes':
// if (value > 0 && value < 0.01) if (value > 0 && value < 0.01)
// return `< 0.01${t('units.timesPerMinute')}` return `< 0.01 ${unit}`
// return Number.parseFloat((Math.floor(value * 100) / 100).toString()) + t('units.timesPerMinute') return `${formatMGT(Number.parseFloat((value).toFixed(2)))} ${unit}`
default:
if (value > 0 && value < 0.01)
return `< 0.01 ${unit}`
return `${Number.parseFloat((value).toFixed(2))} ${unit}`
} }
} }
const [option, setOption] = useState<any>({ const [option, setOption] = useState<any>({
@ -76,14 +113,14 @@ const LineChart = ({ type, data }: LineChartProps) => {
if (axisDimension === 'y') if (axisDimension === 'y')
return convertYValue(value) return convertYValue(value)
else else
return formatDay(value * 1000) return formatDay(value)
// return `自定义格式: ${params.value}`; // return `自定义格式: ${params.value}`;
}, },
}, },
}, },
formatter: (params) => { formatter: (params) => {
let result = `<div class="rgb(102, 102, 102)">${formatDay(params.data[0] * 1000)}<br/></div> let result = `<div class="rgb(102, 102, 102)">${formatDay(params.data[0])}<br/></div>
<div class="overflow-hidden w-full " >` <div class="overflow-hidden w-full " >`
result += `<div class="flex flex-row items-center justify-between"> result += `<div class="flex flex-row items-center justify-between">
<div class="flex flex-row items-center flex-nowrap flex-shrink flex-1 whitespace-normal break-words"> <div class="flex flex-row items-center flex-nowrap flex-shrink flex-1 whitespace-normal break-words">
@ -114,14 +151,9 @@ const LineChart = ({ type, data }: LineChartProps) => {
left: '3%', left: '3%',
right: '4%', right: '4%',
bottom: '3%', bottom: '3%',
top: '4%', top: '15%',
containLabel: true, containLabel: true,
}, },
// toolbox: {
// feature: {
// saveAsImage: {},
// },
// },
xAxis: { xAxis: {
type: 'time', type: 'time',
boundaryGap: false, boundaryGap: false,
@ -136,27 +168,16 @@ const LineChart = ({ type, data }: LineChartProps) => {
return formatTimeUTC(value) return formatTimeUTC(value)
}, },
}, },
// axisLine: {
// lineStyle: {
// color: '#000000', // 设置 x 轴刻度线颜色
// },
// },
// axisTick: {
// lineStyle: {
// color: '#000000', // 设置 x 轴刻度线颜色
// },
// },
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
minInterval: YValueMinInterval[type], minInterval: 0.01,
min: 0, min: 0,
axisLabel: { axisLabel: {
formatter(value: number) { formatter(value: number) {
if(type === 'cpu') if(unit === 'bytes')
return Number.parseFloat((value * 100).toFixed(2)) return formatMGT(Number.parseFloat((value).toFixed(2)))
return Number.parseFloat((value).toFixed(2))
return value
}, },
}, },
}, },
@ -174,25 +195,9 @@ const LineChart = ({ type, data }: LineChartProps) => {
}, },
}) })
// // 处理缺少数据的时间点并补0
// const fillMissingData = () => {
// const filledData = []
// const { startTime, endTime } = timeRange
// const step = getStep(startTime, endTime)
// for (let time = startTime; time <= endTime; time += step) {
// filledData.push({
// timestamp: time, // 用于显示在图例中
// value: data[time] || 0,
// })
// }
// return filledData
// }
useEffect(() => { useEffect(() => {
if (data) { if (data) {
// const filledData = fillMissingData() // const filledData = fillMissingData()
setOption({ setOption({
...option, ...option,
xAxis: { xAxis: {
@ -211,18 +216,22 @@ const LineChart = ({ type, data }: LineChartProps) => {
// min: timeRange.startTime / 1000, // min: timeRange.startTime / 1000,
// max: timeRange.endTime / 1000, // max: timeRange.endTime / 1000,
}, },
series: [ legend: {
{ type: 'scroll',
data: Object.entries(data).map(([key, value]) => [Number(key) / 1000, value]), data: data.map(item => item.legend),
},
series: data.map(item => ({
data: Object.entries(item.chart.chartData)
.map(([key, value]) => [Number(key) / 1000, value]) // 处理微秒级时间戳
.sort((a, b) => a[0] - b[0]),
type: 'line', type: 'line',
smooth: true, smooth: true,
name: DelayLineChartTitleMap[type], name: item.legend,
color: MetricsLineChartColor[type], showSymbol: false,
areaStyle: { // areaStyle: {
color: adjustAlpha(MetricsLineChartColor[type], 0.3), // 设置区域填充颜色 // color: adjustAlpha(MetricsLineChartColor[type], 0.3), // 设置区域填充颜色
}, // },
}, })),
],
}) })
} }
}, [data]) }, [data])

@ -10,6 +10,7 @@ import ParametersInfo from './parameters-info'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { debounce } from 'lodash-es' import { debounce } from 'lodash-es'
import { useGetLanguage } from '@/context/i18n' import { useGetLanguage } from '@/context/i18n'
import Button from '@/app/components/base/button'
type ApoToolsPreviewProps = { type ApoToolsPreviewProps = {
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void; onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void;
@ -33,17 +34,7 @@ const ApoToolsPreview = ({ onSelect, apoToolType, hidePopover }: ApoToolsPreview
const handlePopoverOpen = useCallback((item) => { const handlePopoverOpen = useCallback((item) => {
setToolDetail(item) setToolDetail(item)
}, []) }, [])
const handleSelect = (item) => {
const convertMetricsListToMenuItems = useCallback(() => {
return tools.map((item, index) => (
<div
key={index}
className={cn(
'flex items-center justify-between pl-3 pr-1 w-full rounded-lg hover:bg-state-base-hover cursor-pointer select-none',
item.name === toolDetail?.name && 'bg-state-base-active text-red-500',
)}
onMouseEnter={() => handlePopoverOpen(item)}
onClick={() => {
const params: Record<string, string> = {} const params: Record<string, string> = {}
if (item.parameters) { if (item.parameters) {
item.parameters.forEach((param) => { item.parameters.forEach((param) => {
@ -63,7 +54,16 @@ const ApoToolsPreview = ({ onSelect, apoToolType, hidePopover }: ApoToolsPreview
params, params,
}) })
hidePopover() hidePopover()
}} }
const convertMetricsListToMenuItems = useCallback(() => {
return tools.map((item, index) => (
<div
key={index}
className={cn(
'flex items-center justify-between pl-3 pr-1 w-full rounded-lg hover:bg-state-base-hover cursor-pointer select-none',
item.name === toolDetail?.name && 'bg-state-base-active text-red-500',
)}
onMouseEnter={() => handlePopoverOpen(item)}
> >
<div className="flex grow items-center h-8"> <div className="flex grow items-center h-8">
{/* <BlockIcon {/* <BlockIcon
@ -91,38 +91,42 @@ const ApoToolsPreview = ({ onSelect, apoToolType, hidePopover }: ApoToolsPreview
} }
}, [apoToolType, searchText]) }, [apoToolType, searchText])
return ( return (
<div> <div className="flex max-h-[70vh] overflow-hidden">
<div className="flex">
{/* left */} {/* left */}
<div className="w-[300px]"> <div className="w-[300px] h-full flex flex-col shrink-0">
<div className="mb-3"> <div className="mb-3">
<Input <Input
value={searchText} value={searchText}
onChange={e => setSearchText(e.target.value)} onChange={e => setSearchText(e.target.value)}
/> />
</div> </div>
<div className="p-0.5 space-y-0.5 rounded-[10px]"> <div className="p-0.5 space-y-0.5 rounded-[10px] shrink grow overflow-y-auto">
{tools?.length > 0 && convertMetricsListToMenuItems()} {tools?.length > 0 && convertMetricsListToMenuItems()}
</div> </div>
</div> </div>
<div className="flex-1"> {toolDetail && (<div className="flex-1 min-w-[500px] overflow-auto max-w-[800px]">
{toolDetail && (
<div className="px-4"> <div className="px-4">
<div>{toolDetail.label[language]}</div> <div className='flex'>
<div className='flex-1'>{toolDetail.label[language]}</div>
<Button variant='primary' className=' space-x-2' onClick={() => handleSelect(toolDetail)}>
<div>使</div>
</Button>
</div>
<div className="text-xs text-text-tertiary"> <div className="text-xs text-text-tertiary">
<div className="pt-2"> <div className="pt-2">
<>{toolDetail.description[language]}</> <>{toolDetail.description[language]}</>
</div> </div>
<div className="pt-2"> <div className="pt-2">
{apoToolType === 'select' ? ( {apoToolType === 'apo_select' ? (
<> <>
{['cpu', 'network', 'memory'].includes(toolDetail?.display.type) && <div className="pb-2">{toolDetail.display.unit}</div>} {toolDetail?.display.type === 'metric' && <div className="pb-2">{toolDetail.display.unit}</div>}
{toolDetail?.display?.type && <div>{t(`apo.displayType.${toolDetail?.display?.type}`)}</div>} {toolDetail?.display?.type && <div>{t(`apo.displayType.${toolDetail?.display?.type}`)}</div>}
<div className="px-4 py-2"> <div className="px-4 py-2">
<div className="h-[0.5px] divider-subtle" /> <div className="h-[0.5px] divider-subtle" />
</div> </div>
<div className="pb-2"> <div className="pb-2">
<ToolTrialRun infoSchemas={toolDetail.parameters} /> <ToolTrialRun infoSchemas={toolDetail.parameters} type={toolDetail.display?.type} title={toolDetail?.display?.title}/>
</div> </div>
</> </>
@ -142,9 +146,8 @@ const ApoToolsPreview = ({ onSelect, apoToolType, hidePopover }: ApoToolsPreview
</div> </div>
</div> </div>
</div> </div>
)}
</div>
</div> </div>
)}
</div> </div>
) )
} }

@ -1,10 +1,8 @@
import { memo, useContext } from 'react' import { useGetLanguage } from '@/context/i18n'
import I18n from '@/context/i18n' import { memo } from 'react'
import { getLanguage } from '@/i18n/language'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
const ParametersInfo = ({ parameter }) => { const ParametersInfo = ({ parameter }) => {
const { locale } = useContext(I18n) const language = useGetLanguage()
const language = getLanguage(locale)
const { t } = useTranslation() const { t } = useTranslation()
const getType = (type: string) => { const getType = (type: string) => {
if (type === 'number-input') if (type === 'number-input')

@ -5,6 +5,11 @@ import { InputVarType } from '../../types'
import ParametersInfo from './parameters-info' import ParametersInfo from './parameters-info'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useGetLanguage } from '@/context/i18n' import { useGetLanguage } from '@/context/i18n'
import Button from '@/app/components/base/button'
import { RiLoader2Line } from '@remixicon/react'
import { testApoTools } from '@/service/tools'
import DataDisplay from '../../run/data-display'
const i18nPrefix = 'workflow.singleRun'
const varTypeToInputVarType = (type: string) => { const varTypeToInputVarType = (type: string) => {
if(type === 'string') if(type === 'string')
return InputVarType.textInput return InputVarType.textInput
@ -12,11 +17,14 @@ const varTypeToInputVarType = (type: string) => {
return InputVarType.singleFile return InputVarType.singleFile
return type return type
} }
const ToolTrialRun = ({ infoSchemas }) => { const ToolTrialRun = ({ infoSchemas, type, title }) => {
const [formValues, setFormValues] = useState(null) const [formValues, setFormValues] = useState(null)
const [formInputs, setFormInputs] = useState([]) const [formInputs, setFormInputs] = useState([])
const language = useGetLanguage() const language = useGetLanguage()
const { t } = useTranslation() const { t } = useTranslation()
const [isRunning, setIsRunning] = useState(false)
const [displayData, setDisplayData] = useState(null)
useEffect(() => { useEffect(() => {
const formValues = infoSchemas?.reduce((acc, item) => { const formValues = infoSchemas?.reduce((acc, item) => {
acc[item.name] = null acc[item.name] = null
@ -32,11 +40,39 @@ const ToolTrialRun = ({ infoSchemas }) => {
})) }))
setFormValues(formValues) setFormValues(formValues)
setFormInputs(formInputs) setFormInputs(formInputs)
setDisplayData(null)
}, [infoSchemas]) }, [infoSchemas])
const handleRun = async () => {
const data = await testApoTools({
type,
title,
params: formValues,
startTime: formValues?.startTime,
endTime: formValues?.endTime,
})
if(data?.data?.code)
setDisplayData({ type: 'error', data: data.data?.message })
else
setDisplayData(data)
}
return <> return <>
<div className='py-2 text-text-primary system-sm-semibold-uppercase'>{t('tools.setBuiltInTools.parameters')}</div> <div className='py-2 text-text-primary system-sm-semibold-uppercase'>
{/* {t('tools.setBuiltInTools.parameters')} */}
</div>
<Form inputs={formInputs} values={formValues} onChange={newValues => setFormValues(newValues) } <Form inputs={formInputs} values={formValues} onChange={newValues => setFormValues(newValues) }
></Form> ></Form>
<div className='w-full flex pt-2'>
<Button disabled={isRunning} variant='primary' className='w-0 grow space-x-2' onClick={handleRun}>
{isRunning && <RiLoader2Line className='animate-spin w-4 h-4 text-white' />}
<div>{t(`${i18nPrefix}.${isRunning ? 'running' : 'startRun'}`)}</div>
</Button>
</div>
{
displayData && <DataDisplay dataObj={displayData} />
}
</> </>
} }
export default memo(ToolTrialRun) export default memo(ToolTrialRun)

@ -1,20 +1,25 @@
import type { BlockEnum } from '../types' import { BlockEnum } from '../types'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { initApoToolsEntry } from '../apo/constant' import { useEffect, useState } from 'react'
import { useState } from 'react'
import type { ToolDefaultValue } from './types' import type { ToolDefaultValue } from './types'
import { Popover } from 'antd' import { Popover } from 'antd'
import ApoToolsPreview from '../apo/tool-preview/apo-tools-preview' import ApoToolsPreview from '../apo/tool-preview/apo-tools-preview'
import type { ApoToolTypeInfo } from '../apo/types' import type { ApoToolTypeInfo } from '../apo/types'
import { fetchApoNode } from '@/service/tools'
import { useGetLanguage } from '@/context/i18n'
import BlockIcon from '../block-icon'
type APOToolsProps = { type APOToolsProps = {
searchText: string; searchText: string;
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
} }
const APOTools = ({ searchText, onSelect }: APOToolsProps) => { const APOTools = ({ searchText, onSelect }: APOToolsProps) => {
const language = useGetLanguage()
const [openKey, setOpenKey] = useState({ const [openKey, setOpenKey] = useState({
select: false, apo_select: false,
analysis: false, apo_analysis: false,
apo_rule: false,
}) })
const [apoNodes, setApoNodes] = useState([])
const hide = (key: ApoToolTypeInfo) => { const hide = (key: ApoToolTypeInfo) => {
setOpenKey(prev => ({ setOpenKey(prev => ({
...prev, ...prev,
@ -28,31 +33,39 @@ const APOTools = ({ searchText, onSelect }: APOToolsProps) => {
[key]: newOpen, [key]: newOpen,
})) }))
} }
const getApoNodes = async () => {
const data = await fetchApoNode()
setApoNodes(data)
}
useEffect(() => {
getApoNodes()
}, [])
return ( return (
<> <>
<div className="mb-1 last-of-type:mb-0"> <div className="mb-1 last-of-type:mb-0">
{Object.entries(initApoToolsEntry).map(([key, tool]) => ( {apoNodes?.map(node => (
<Popover <Popover
key={key} key={node.id}
placement='rightTop' placement='rightTop'
trigger={['click']} trigger={['click']}
open={openKey[key]} open={openKey[node.id]}
onOpenChange={newOpen => handleOpenChange(key, newOpen)} onOpenChange={newOpen => handleOpenChange(node.id, newOpen)}
content={ content={
<ApoToolsPreview onSelect={onSelect} apoToolType={key} hidePopover={() => hide(key)}/> <ApoToolsPreview onSelect={onSelect} apoToolType={node.name} hidePopover={() => hide(node.name)}/>
} }
> >
<div <div
key={tool.type} key={node.id}
className="flex items-center px-3 w-full h-8 rounded-lg hover:bg-state-base-hover cursor-pointer" className="flex items-center px-3 w-full h-8 rounded-lg hover:bg-state-base-hover cursor-pointer"
> >
{/* <BlockIcon <BlockIcon
className="mr-2 shrink-0" className="mr-2 shrink-0"
type={BlockEnum.Tool} type={BlockEnum.Tool}
toolIcon={tool.icon} toolIcon={node.icon}
/> */} />
<div className={cn('grow text-sm text-gray-900 truncate')}> <div className={cn('grow text-sm text-gray-900 truncate')}>
{tool.label} {node?.label[language]}
</div> </div>
</div> </div>
</Popover> </Popover>

@ -99,8 +99,8 @@ const NodeSelector: FC<NodeSelectorProps> = ({
return t('workflow.tabs.searchTool') return t('workflow.tabs.searchTool')
return '' return ''
}, [activeTab, t]) }, [activeTab, t])
const TabContent = () => <div className={`rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg ${popupClassName}`}> const TabContent = () => <div className={`flex flex-col overflow-hidden rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg ${popupClassName}`}>
<div className='px-2 pt-2' onClick={e => e.stopPropagation()}> <div className='px-2 pt-2 ' onClick={e => e.stopPropagation()}>
{activeTab === TabsEnum.Blocks && ( {activeTab === TabsEnum.Blocks && (
<Input <Input
showLeftIcon showLeftIcon
@ -124,6 +124,8 @@ const NodeSelector: FC<NodeSelectorProps> = ({
)} )}
</div> </div>
<div className='h-0 grow'>
<Tabs <Tabs
activeTab={activeTab} activeTab={activeTab}
onActiveTabChange={handleActiveTabChange} onActiveTabChange={handleActiveTabChange}
@ -134,6 +136,8 @@ const NodeSelector: FC<NodeSelectorProps> = ({
noBlocks={noBlocks} noBlocks={noBlocks}
/> />
</div> </div>
</div>
return absolute return absolute
? <PortalToFollowElem ? <PortalToFollowElem
placement={placement} placement={placement}

@ -34,7 +34,7 @@ const Tabs: FC<TabsProps> = ({
const { data: workflowTools } = useAllWorkflowTools() const { data: workflowTools } = useAllWorkflowTools()
return ( return (
<div onClick={e => e.stopPropagation()}> <div onClick={e => e.stopPropagation()} className='h-full flex flex-col'>
{ {
!noBlocks && ( !noBlocks && (
<div className='flex items-center px-3 border-b-[0.5px] border-divider-subtle'> <div className='flex items-center px-3 border-b-[0.5px] border-divider-subtle'>
@ -59,12 +59,12 @@ const Tabs: FC<TabsProps> = ({
} }
{ {
activeTab === TabsEnum.Blocks && !noBlocks && ( activeTab === TabsEnum.Blocks && !noBlocks && (
<><APOTools onSelect={onSelect} searchText={searchText}/> <div className='h-0 grow overflow-y-auto'><APOTools onSelect={onSelect} searchText={searchText}/>
<Blocks <Blocks
searchText={searchText} searchText={searchText}
onSelect={onSelect} onSelect={onSelect}
availableBlocksTypes={availableBlocksTypes} availableBlocksTypes={availableBlocksTypes}
/></> /></div>
) )
} }
{ {

@ -7,17 +7,19 @@ import AlertList from '../../base/alert'
import LogContent from '../../base/log/LogContent' import LogContent from '../../base/log/LogContent'
type DataDisplayProps = { type DataDisplayProps = {
data: string; data?: string;
dataObj?: any
} }
const TopologyCom = ({ data }) => { const TopologyCom = ({ data }) => {
const topologyData = usePrepareTopologyData(data) const topologyData = usePrepareTopologyData(data)
return <div className=' w-full h-[400px] relative'><Topology data={topologyData} /></div> return <div className=' w-full h-[400px] relative'><Topology data={topologyData} /></div>
} }
const DataDisplay = ({ data }: DataDisplayProps) => { const DataDisplay = ({ data, dataObj }: DataDisplayProps) => {
// const [chartData,setChartData] = useState<any>(null) // const [chartData,setChartData] = useState<any>(null)
const { t } = useTranslation() const { t } = useTranslation()
let chartData: any = null let chartData: any = null
if(data) {
try { try {
chartData = JSON.parse(data) chartData = JSON.parse(data)
if(chartData.data?.message) chartData = { type: 'error', data: chartData.data?.message } if(chartData.data?.message) chartData = { type: 'error', data: chartData.data?.message }
@ -26,7 +28,11 @@ const DataDisplay = ({ data }: DataDisplayProps) => {
console.error('JSON 解析失败:', error) console.error('JSON 解析失败:', error)
chartData = {} // 返回一个空对象或其他默认值 chartData = {} // 返回一个空对象或其他默认值
} }
console.log(chartData) }
else{
chartData = dataObj
}
return ( return (
<div className="relative w-full"> <div className="relative w-full">
{ {
@ -36,8 +42,8 @@ const DataDisplay = ({ data }: DataDisplayProps) => {
{t('apo.chart.chartTitle')} {t('apo.chart.chartTitle')}
</div> </div>
<div className='px-1'> <div className='px-1'>
{['cpu', 'network', 'memory'].includes(chartData?.type) && ( {chartData?.type === 'metric' && (
<LineChart type={chartData?.type} data={chartData?.data} /> <LineChart data={chartData?.data?.timeseries || []} unit={chartData?.unit} />
)} )}
{chartData?.type === 'topology' && ( {chartData?.type === 'topology' && (
<TopologyCom data={chartData?.data} /> <TopologyCom data={chartData?.data} />

@ -14,6 +14,7 @@ const translation = {
alert: 'Alert events', alert: 'Alert events',
log: 'Log data', log: 'Log data',
custom: 'Custom query return data', custom: 'Custom query return data',
metric: 'Metric Line Chart',
}, },
} }

@ -14,6 +14,7 @@ const translation = {
alert: '告警事件', alert: '告警事件',
log: '日志数据', log: '日志数据',
custom: '自定义查询返回数据', custom: '自定义查询返回数据',
metric: '指标折线图',
}, },
} }

@ -167,3 +167,10 @@ export const fetchApoTools = (apoToolType: string, queryText: string) => {
}, },
}) })
} }
export const testApoTools = (params: any) => {
return post('/workspaces/current/tools/apo/preview', { body: params })
}
export const fetchApoNode = () => {
return get('/workspaces/current/tools/apo/list')
}

Loading…
Cancel
Save