From 3f86c7e99fc3ace35d17a47e1863e11714553fb2 Mon Sep 17 00:00:00 2001 From: rivery Date: Wed, 17 Aug 2022 18:21:57 +0800 Subject: [PATCH] feat: add metric detail panel --- src/components/MachineDetail/index.less | 82 ---- src/components/MachineDetail/index.tsx | 139 ------- src/components/MetricsFilterPanel/index.tsx | 1 + src/config/locale/en-US/base.json | 5 +- src/config/locale/zh-CN/base.json | 5 +- src/pages/MachineDashboard/Detail/index.less | 19 +- src/pages/MachineDashboard/Detail/index.tsx | 79 ++-- src/pages/MachineDashboard/index.less | 4 +- src/pages/MainPage/routes.tsx | 32 +- src/pages/MetricDetail/index.module.less | 66 ++++ src/pages/MetricDetail/index.tsx | 377 +++++++++++++++++++ src/pages/ServiceDashboard/Detail/index.less | 9 + src/pages/ServiceDashboard/Detail/index.tsx | 45 ++- src/store/models/machine.ts | 1 - src/utils/chart/chart.ts | 13 +- src/utils/dashboard.ts | 6 +- 16 files changed, 607 insertions(+), 276 deletions(-) delete mode 100644 src/components/MachineDetail/index.less delete mode 100644 src/components/MachineDetail/index.tsx create mode 100644 src/pages/MetricDetail/index.module.less create mode 100644 src/pages/MetricDetail/index.tsx diff --git a/src/components/MachineDetail/index.less b/src/components/MachineDetail/index.less deleted file mode 100644 index a4aa5a94..00000000 --- a/src/components/MachineDetail/index.less +++ /dev/null @@ -1,82 +0,0 @@ -.dashboard-detail { - background: #fff; - padding: 20px 30px; - height: 100%; - display: flex; - flex-direction: column; - - > .filter { - display: flex; - justify-content: space-between; - align-items: flex-end; - flex-wrap: wrap; - position: relative; - - > .time-range { - > .ant-radio-group { - > label { - width: 68px; - height: 34px; - line-height: 34px; - font-size: 12px; - text-align: center; - } - } - - > .ant-picker-range { - height: 34px; - margin-left: 16px; - } - } - - > .right-panel { - display: flex; - - > .ant-form-item { - margin: 0; - - .ant-select-selector { - height: 34px; - font-size: 12px; - } - } - - > .ant-form-item + .ant-form-item { - margin-left: 16px; - } - - .filter-in-icon { - margin-right: 16px; - } - - .ant-input-number { - margin-right: 5px; - } - - .metric { - margin-right: 30px; - } - - .metric-info-icon { - position: absolute; - font-size: 16px; - margin: 8px 10px 0 10px; - } - } - - > .btn-icon-with-desc { - position: absolute; - top: 50px; - right: 20px; - } - } - - > .detail-content { - margin-top: 100px; - flex: 1; - - > .nebula-chart { - height: 100%; - } - } -} diff --git a/src/components/MachineDetail/index.tsx b/src/components/MachineDetail/index.tsx deleted file mode 100644 index 8c99d7b2..00000000 --- a/src/components/MachineDetail/index.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { DETAIL_DEFAULT_RANGE, TIMEOPTIONS } from '@/utils/dashboard'; -import { DatePicker, Form, Radio } from 'antd'; -import React, { HTMLProps } from 'react'; -import dayjs from 'dayjs'; - -import './index.less'; -import intl from 'react-intl-universal'; -import Icon from '../Icon'; -import { DashboardSelect, Option } from '../DashboardSelect'; -import { MetricPopover } from '../MetricPopover'; -import { SUPPORT_METRICS } from '@/utils/promQL'; - -interface IProps extends HTMLProps { - children: any; - onTimeChange?: (start: number, end: number) => void; - startTimestamps?: number; - endTimestamps?: number; - title?: string; - typeOptions?: any[]; - currentType?: string; - metricOptions?: typeof SUPPORT_METRICS.cpu; - currentMetricOption?: typeof SUPPORT_METRICS.cpu[0]; - onTypeChange?: (type: string) => void; - onMetricChange?: (metric: string, metricOption: any) => void; - onBaseLineEdit?: () => void; -} - -class MachineDetail extends React.PureComponent { - handleTimeButtonClick = e => { - const { onTimeChange } = this.props; - const endTimeStamps = Date.now(); - const startTimeStamps = endTimeStamps - e.target.value; - if (onTimeChange) { - onTimeChange(startTimeStamps, endTimeStamps); - } - }; - - handleTimeRangeChange = ([startDate, endDate]) => { - const { onTimeChange } = this.props; - if (onTimeChange) { - onTimeChange(+startDate, +endDate); - } - }; - - handleBaseLineEdit = () => { - const { onBaseLineEdit } = this.props; - if (onBaseLineEdit) { - onBaseLineEdit(); - } - }; - - disabledDate = current => - current < dayjs().subtract(14, 'days').endOf('day') || - current > dayjs().endOf('day'); - - render() { - const now = Date.now(); - const { - children, - typeOptions, - currentType, - onTypeChange, - metricOptions, - currentMetricOption, - onMetricChange, - startTimestamps = now - DETAIL_DEFAULT_RANGE, - endTimestamps = now, - } = this.props; - const interval = endTimestamps - startTimestamps; - const startDate = dayjs(startTimestamps); - const endDate = dayjs(endTimestamps); - return ( -
-
-
- - {TIMEOPTIONS.map(option => ( - - {intl.get(`component.dashboardDetail.${option.name}`)} - - ))} - - -
-
- {typeOptions && ( - - - {typeOptions.map(option => ( - - ))} - - - )} - {metricOptions && ( - - - {metricOptions.map(option => ( - - ))} - - - - )} -
-
- - {intl.get('common.baseLine')} -
-
-
{children}
-
- ); - } -} - -export default MachineDetail; diff --git a/src/components/MetricsFilterPanel/index.tsx b/src/components/MetricsFilterPanel/index.tsx index 85c546c0..5901d6a2 100644 --- a/src/components/MetricsFilterPanel/index.tsx +++ b/src/components/MetricsFilterPanel/index.tsx @@ -142,6 +142,7 @@ const MetricsFilterPanel = (props: IProps, ref) => { allowClear className={styles.metricSelect} mode="multiple" + placeholder={intl.get('base.searchMetric')} onChange={handleMetricsSelectChange} style={{ minWidth: '250px', maxWidth: '500px' }}> { diff --git a/src/config/locale/en-US/base.json b/src/config/locale/en-US/base.json index 7b8c460a..49449e26 100644 --- a/src/config/locale/en-US/base.json +++ b/src/config/locale/en-US/base.json @@ -4,5 +4,8 @@ "spaceChartInstance": "instance", "spaceChartDiskname": "Disk Name", "spaceChartMountpoint": "Mount Point", - "spaceChartDiskused": "Disk Used" + "spaceChartDiskused": "Disk Used", + "metricDetail": "Metric Detail", + "metricMonitor": "Metric Monitor", + "searchMetric": "Please Search Metric" } \ No newline at end of file diff --git a/src/config/locale/zh-CN/base.json b/src/config/locale/zh-CN/base.json index 0700d703..b7026580 100644 --- a/src/config/locale/zh-CN/base.json +++ b/src/config/locale/zh-CN/base.json @@ -4,5 +4,8 @@ "spaceChartInstance": "实例", "spaceChartDiskname": "磁盘名称", "spaceChartMountpoint": "挂载点", - "spaceChartDiskused": "已使用" + "spaceChartDiskused": "已使用", + "metricDetail": "指标详情", + "metricMonitor": "指标监控", + "searchMetric": "请搜索指标" } \ No newline at end of file diff --git a/src/pages/MachineDashboard/Detail/index.less b/src/pages/MachineDashboard/Detail/index.less index b1a90dc6..29e1c8eb 100644 --- a/src/pages/MachineDashboard/Detail/index.less +++ b/src/pages/MachineDashboard/Detail/index.less @@ -79,12 +79,6 @@ justify-content: center; } } - - >.base-line { - position: absolute; - top: 15px; - right: 24px; - } } @media screen and (max-width: 1960px) and (min-width: 1200px) { @@ -111,4 +105,15 @@ height: 16px; margin-left: 6px; width: 16px; -} \ No newline at end of file +} + +.action-icons { + position: absolute; + top: 15px; + right: 24px; + display: flex; + align-items: center; + min-width: 80px; + justify-content: space-between; +} + diff --git a/src/pages/MachineDashboard/Detail/index.tsx b/src/pages/MachineDashboard/Detail/index.tsx index 620bcb13..3c96ee9b 100644 --- a/src/pages/MachineDashboard/Detail/index.tsx +++ b/src/pages/MachineDashboard/Detail/index.tsx @@ -9,6 +9,7 @@ import { getBaseLineByUnit, getDataByType, getDiskData, + getMachineRouterPath, getProperTickInterval, } from '@/utils/dashboard'; import { configDetailChart, updateDetailChart } from '@/utils/chart/chart'; @@ -21,6 +22,7 @@ import Icon from '@/components/Icon'; import BaseLineEditModal from '@/components/BaseLineEditModal'; import './index.less'; import { IMachineMetricOption } from '@/utils/interface'; +import { RouteProps, useHistory } from 'react-router-dom'; const mapDispatch: any = (dispatch: IDispatch) => ({ asyncUpdateBaseLine: (key, value) => @@ -38,7 +40,7 @@ const mapState = (state: IRootState) => ({ }); interface IProps extends ReturnType, - ReturnType { + ReturnType, RouteProps { type: string; asyncGetDataSourceByRange: (params: { start: number; @@ -64,6 +66,8 @@ function Detail(props: IProps) { const [showLoading, setShowLoading] = useState(false); + const history = useHistory(); + const metricCharts: any = useMemo(() => (metricOptions || []).map( (metric, i) => ({ metric, @@ -159,23 +163,27 @@ function Detail(props: IProps) { const [startTimestamps, endTimestamps] = calcTimeRange(metricsFilterValues.timeRange); metricCharts.forEach((chart, i) => { if (chart.chartInstance) { - const data = type === 'disk' ? - getDiskData({ - data: dataSources[i] || [], - type: metricsFilterValues.instanceList, - nameObj: dataTypeObj, - aliasConfig, - }) : - getDataByType({ - data: dataSources[i] || [], - type: metricsFilterValues.instanceList, - nameObj: dataTypeObj, - aliasConfig, - }); - setMaxNum(100); + const data = type === 'disk' ? + getDiskData({ + data: dataSources[i] || [], + type: metricsFilterValues.instanceList, + nameObj: dataTypeObj, + aliasConfig, + }) : + getDataByType({ + data: dataSources[i] || [], + type: metricsFilterValues.instanceList, + nameObj: dataTypeObj, + aliasConfig, + }); + const values = data.map(d => d.value) as number[] ; + const maxNum = values.length > 0 ? Math.floor(Math.max(...values) * 100) / 100 : undefined; + const minNum = values.length > 0 ? Math.floor(Math.min(...values) * 100) / 100 : undefined; updateDetailChart(chart.chartInstance, { type, tickInterval: getProperTickInterval(endTimestamps - startTimestamps), + maxNum, + minNum }).changeData(data); chart.chartInstance.autoFit = true; } @@ -203,18 +211,29 @@ function Detail(props: IProps) { } } + const getViewPath = (path: string): string => { + if (cluster?.id) { + return getMachineRouterPath(path, cluster.id); + } + return path; + } + const shouldShow = (metricItem) => { return curMetricOptions.find(item => item.metric === metricItem.metric) } + const handleViewDetail = (metricItem) => () => { + history.push(getViewPath(`/metrics-detail/${type}/${metricItem.metric.metric}`)); + } + return (
- { metricCharts.map((metricChart, i) => ( -
+
{metricChart.metric.metric}
-
- - {intl.get('common.baseLine')} +
+
+ +
+
+ + {intl.get('common.baseLine')} +
)) diff --git a/src/pages/MachineDashboard/index.less b/src/pages/MachineDashboard/index.less index 65f488dd..8dc419ad 100644 --- a/src/pages/MachineDashboard/index.less +++ b/src/pages/MachineDashboard/index.less @@ -10,8 +10,8 @@ .common-header { background: #fff; - margin: 15px 0; - justify-content: flex-start; + margin: 15px 0; + justify-content: flex-start; padding-bottom: 0; height: auto; } diff --git a/src/pages/MainPage/routes.tsx b/src/pages/MainPage/routes.tsx index 147b6c92..431f918e 100644 --- a/src/pages/MainPage/routes.tsx +++ b/src/pages/MainPage/routes.tsx @@ -1,4 +1,3 @@ -// import { lazy } from 'react'; import intl from 'react-intl-universal'; import LoadDetail from '../MachineDashboard/Detail/LoadDetail'; import MachineDashboard from '@/pages/MachineDashboard'; @@ -15,6 +14,7 @@ import CPUDetail from '@/pages/MachineDashboard/Detail/CPUDetail'; import DiskDetail from '@/pages/MachineDashboard/Detail/DiskDetail'; import NetworkDetail from '@/pages/MachineDashboard/Detail/NetworkDetail'; import MemoryDetail from '@/pages/MachineDashboard/Detail/MemoryDetail'; +import MetricDetail from '@/pages/MetricDetail'; export const MenuList = [ { @@ -162,6 +162,21 @@ export const RoutesList: any = [ title: intl.get('common.machine'), }, }, + { + path: '/metrics-detail/:metricType/:metricName', + component: MetricDetail, + exact: true, + headerConfig: { + breadcrumb: [ + { + path: '#', + breadcrumbName: intl.get('common.dashboard'), + }, + ], + showBackBtn: true, + title: intl.get('common.machine'), + }, + }, { path: '/machine/cpu', component: CPUDetail, @@ -296,6 +311,21 @@ export const RoutesList: any = [ extra: SERVICE_VIEWS, }, }, + { + path: '/service-metrics/detail/:metricName', + component: MetricDetail, + exact: true, + headerConfig: { + breadcrumb: [ + { + path: '#', + breadcrumbName: intl.get('common.dashboard'), + }, + ], + showBackBtn: true, + title: intl.get('common.machine'), + }, + }, { path: '/service/graph-metrics', component: ServiceDetail, diff --git a/src/pages/MetricDetail/index.module.less b/src/pages/MetricDetail/index.module.less new file mode 100644 index 00000000..b8c5ca97 --- /dev/null +++ b/src/pages/MetricDetail/index.module.less @@ -0,0 +1,66 @@ +.dashboard-detail { + height: 100%; +} + +.common-header { + background: #fff; + padding: 15px; + justify-content: flex-start; + padding-bottom: 0; + height: auto; +} + +.detail-content { + margin-top: 24px; + flex: 1; + display: flex; + flex-direction: row; + flex-wrap: wrap; + width: 100%; + + :global { + .chart-item { + height: calc(100vh - 290px); + border: 2px solid rgba(0, 0, 0, .06); + width: 100%; + margin-bottom: 24px; + border-radius: 3px; + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background: #fff; + margin-right: 15px; + + .chart-title { + width: 100%; + line-height: 30px; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + margin-top: 12px; + font-size: 20px; + } + + .chart-content { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + position: relative; + + .nebula-chart { + height: 100%; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + } + } + } + } +} \ No newline at end of file diff --git a/src/pages/MetricDetail/index.tsx b/src/pages/MetricDetail/index.tsx new file mode 100644 index 00000000..3d68ffae --- /dev/null +++ b/src/pages/MetricDetail/index.tsx @@ -0,0 +1,377 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { connect } from 'react-redux'; +import intl from 'react-intl-universal'; + +import MetricsFilterPanel from '@/components/MetricsFilterPanel'; +import ServiceMetricsFilterPanel from '@/components/ServiceMetricsFilterPanel'; +import { IDispatch, IRootState } from '@/store'; + +import styles from './index.module.less'; +import LineChart from '@/components/Charts/LineChart'; +import { useParams } from 'react-router-dom'; +import { calcTimeRange, getBaseLineByUnit, getDataByType, getDiskData, getMetricsUniqName, getProperTickInterval } from '@/utils/dashboard'; +import { MetricScene } from '@/utils/interface'; +import { SUPPORT_METRICS } from '@/utils/promQL'; +import { Chart } from '@antv/g2'; +import { configDetailChart, updateDetailChart } from '@/utils/chart/chart'; +import { shouldCheckCluster } from '@/utils'; +import { Popover, Spin } from 'antd'; +import Icon from '@/components/Icon'; +import BaseLineEditModal from '@/components/BaseLineEditModal'; + +interface Props + extends ReturnType, + ReturnType { +} + +enum MetricTypeName { + Disk = 'disk', + Cpu = 'cpu', + Memory = 'memory', + Load = 'load', + Network = 'network', + Graphd = 'graphd', + Metad = 'metad', + Storaged = 'storaged' +} + +function isServiceMetric(metricType: MetricTypeName) { + return metricType === MetricTypeName.Metad || metricType === MetricTypeName.Graphd || metricType === MetricTypeName.Storaged; +} + +function getMetricSecene(metricType: MetricTypeName) { + switch (metricType) { + case MetricTypeName.Disk: + return MetricScene.DISK; + case MetricTypeName.Cpu: + return MetricScene.CPU; + case MetricTypeName.Memory: + return MetricScene.MEMORY; + case MetricTypeName.Load: + return MetricScene.LOAD; + case MetricTypeName.Network: + return MetricScene.NETWORK; + default: + return MetricScene.SERVICE; + } +} + +const mapState = (state: IRootState) => ({ + aliasConfig: state.app.aliasConfig, + cluster: (state as any).cluster?.cluster, + serviceMetric: state.serviceMetric, + instances: state.machine.instanceList, + serviceInstanceList: state.service.instanceList, + metricsFilterValues: state.machine.metricsFilterValues, + serviceMetricsFilterValues: state.service.metricsFilterValues, + serviceLoading: state.loading.models.service, + machineLoading: state.loading.models.machine, +}); + +const mapDispatch: any = (dispatch: IDispatch) => ({ + asyncUpdateBaseLine: (key, value) => + dispatch.setting.update({ + [key]: value, + }), + updateMetricsFiltervalues: dispatch.machine.updateMetricsFiltervalues, + updateServiceMetricsFiltervalues: dispatch.service.updateMetricsFiltervalues, + asyncFetchMachineMetricsData: dispatch.machine.asyncGetMetricsData, + asyncFetchServiceMetricsData: dispatch.service.asyncGetMetricsData, + asyncGetSpaces: dispatch.serviceMetric.asyncGetSpaces, +}); + +let pollingTimer: any; + +function MetricDetail(props: Props) { + + const { cluster, type, metricsFilterValues, serviceMetricsFilterValues, + updateMetricsFiltervalues, updateServiceMetricsFiltervalues, instances, serviceInstanceList, + asyncFetchMachineMetricsData, aliasConfig, serviceMetric, asyncFetchServiceMetricsData, asyncGetSpaces, + serviceLoading, machineLoading } = props; + + const { metricName, metricType } = useParams(); + + const [dataSource, setDataSource] = useState([]); + + const [showLoading, setShowLoading] = useState(false); + + const curMetricsFilterValues = useMemo(() => isServiceMetric(metricType) ? serviceMetricsFilterValues : metricsFilterValues, [type, metricsFilterValues, serviceMetricsFilterValues]); + + const metricOption = useMemo(() => { + let metrics: any[] = []; + if (isServiceMetric(metricType)) { + metrics = serviceMetric[metricType].map(item => ( + { + metric: item.metric, + isSpaceMetric: item.isSpaceMetric, + metricType: item.metricType, + valueType: item.valueType, + })) + } else { + metrics = SUPPORT_METRICS[metricType]; + } + const metricItem = metrics.find(item => item.metric === metricName) || { + metric: '', + valueType: '', + metricType: [], + } + return metricItem + }, [metricName, metricType, serviceMetric]) + + useEffect(() => { + if (isServiceMetric(metricType)) { + setShowLoading(serviceLoading && curMetricsFilterValues.frequency === 0) + } else { + setShowLoading(machineLoading && curMetricsFilterValues.frequency === 0) + } + }, [metricType, serviceLoading, machineLoading, curMetricsFilterValues.frequency]); + + const metricChartRef = useRef(); + + const metricChart: any = useMemo(() => { + if (metricChartRef.current) { + const res = { + ...metricChartRef.current, + metric: metricOption, + } + return res + } + const res = { + metric: metricOption, + chartInstance: undefined, + baseLine: undefined, + } + metricChartRef.current = res; + return res + }, [metricOption]); + + useEffect(() => { + if (!isServiceMetric(metricType)) return; + const [start, end] = calcTimeRange(curMetricsFilterValues.timeRange); + if (shouldCheckCluster()) { + if (cluster?.id) { + asyncGetSpaces({ + clusterID: cluster.id, + start, + end + }) + } + } else { + asyncGetSpaces({ + start, + end + }) + } + }, [curMetricsFilterValues.timeRange, cluster, metricType]) + + useEffect(() => { + if (pollingTimer) { + clearTimeout(pollingTimer); + } + if (shouldCheckCluster()) { + if (cluster?.id) { + pollingData(); + } + } else { + pollingData(); + } + }, [cluster, curMetricsFilterValues.frequency, curMetricsFilterValues.timeRange, + curMetricsFilterValues.metricType, curMetricsFilterValues.period, curMetricsFilterValues.space, metricOption]); + + const pollingData = () => { + getData(); + if (curMetricsFilterValues.frequency > 0) { + pollingTimer = setTimeout(pollingData, curMetricsFilterValues.frequency); + } + }; + + useEffect(() => { + updateChart(); + }, [curMetricsFilterValues.instanceList, dataSource]) + + const updateChart = () => { + const [startTimestamps, endTimestamps] = calcTimeRange(curMetricsFilterValues.timeRange); + const metricScene = getMetricSecene(metricType); + if (metricChart.chartInstance) { + const data = metricType === MetricTypeName.Disk ? + getDiskData({ + data: dataSource || [], + type: curMetricsFilterValues.instanceList, + nameObj: getMetricsUniqName(metricScene), + aliasConfig, + }) : + getDataByType({ + data: dataSource || [], + type: curMetricsFilterValues.instanceList, + nameObj: getMetricsUniqName(metricScene), + aliasConfig, + }); + const values = data.map(d => d.value) as number[]; + const maxNum = values.length > 0 ? Math.floor(Math.max(...values) * 100) / 100 : undefined; + const minNum = values.length > 0 ? Math.floor(Math.min(...values) * 100) / 100 : undefined; + updateDetailChart(metricChart.chartInstance, { + type, + tickInterval: getProperTickInterval(endTimestamps - startTimestamps), + maxNum, + minNum, + }).changeData(data); + metricChart.chartInstance.autoFit = true; + } + }; + + const metricTypeMap = useMemo(() => { + const map = {}; + if (metricOption && isServiceMetric(metricType)) { + metricOption.metricType.forEach(type => { + const { key, value } = type; + const metricItem = { + metric: metricOption.metric, + isSpaceMetric: metricOption.isSpaceMetric, + metricType: metricOption.metricType, + valueType: metricOption.valueType, + metricFunction: value, + }; + if (!map[key]) { + map[key] = [metricItem]; + } else { + map[key].push(metricItem) + } + }) + } + return map; + }, [metricType, metricOption]) + + const getData = async () => { + const [startTimestamps, endTimestamps] = calcTimeRange(curMetricsFilterValues.timeRange); + if (isServiceMetric(metricType)) { + const { period, space, metricType: aggregration } = curMetricsFilterValues; + const item = metricTypeMap[aggregration]?.find(metricItem => metricItem.metric === metricChart.metric.metric); + if (item) { + asyncFetchServiceMetricsData({ + query: item.metricFunction + period, + start: startTimestamps, + end: endTimestamps, + space: metricType === MetricTypeName.Graphd ? space : undefined, + clusterID: cluster?.id, + }).then(res => { + setDataSource(res); + }); + } + } else { + asyncFetchMachineMetricsData({ + start: startTimestamps, + end: endTimestamps, + metric: metricChart.metric.metric, + clusterID: cluster?.id, + }).then(res => { + setDataSource(res); + }); + } + }; + + const handleMetricChange = async values => { + if (isServiceMetric(metricType)) { + updateServiceMetricsFiltervalues(values) + } else { + updateMetricsFiltervalues(values); + } + }; + + const handleRefresh = () => { + if (isServiceMetric(metricType)) { + setShowLoading(!!serviceLoading); + } else { + setShowLoading(!!machineLoading); + } + getData(); + } + + const renderChart = (chartInstance: Chart) => { + const [startTimestamps, endTimestamps] = calcTimeRange(curMetricsFilterValues.timeRange); + metricChart.chartInstance = chartInstance; + configDetailChart(chartInstance, { + tickInterval: getProperTickInterval(endTimestamps - startTimestamps), + valueType: metricChart.metric.valueType, + }); + } + + const handleBaseLineEdit = () => { + BaseLineEditModal.show({ + baseLine: metricChart.baseLine, + valueType: metricChart.metric.valueType, + onOk: (values) => handleBaseLineChange(metricChart, values), + }); + }; + + const handleBaseLineChange = async (metricChart, values) => { + const { baseLine, unit } = values; + metricChart.baseLine = getBaseLineByUnit({ + baseLine, + unit, + valueType: metricChart.valueType, + }); + metricChart.chartRef.updateBaseline(metricChart.baseLine); + }; + + return ( + +
+
+ { + isServiceMetric(metricType) ? ( + + ) : ( + + ) + } +
+
+
+
+ {metricChart.metric?.metric} + {intl.get(`metric_description.${metricChart.metric?.metric}`)}
+ } + > + + +
+
+ metricChart.chartRef = ref} + renderChart={renderChart} + /> +
+
+
+ + {intl.get('common.baseLine')} +
+
+
+
+
+ + ) +} + +export default connect(mapState, mapDispatch)(MetricDetail); \ No newline at end of file diff --git a/src/pages/ServiceDashboard/Detail/index.less b/src/pages/ServiceDashboard/Detail/index.less index 522d02e8..c3817c22 100644 --- a/src/pages/ServiceDashboard/Detail/index.less +++ b/src/pages/ServiceDashboard/Detail/index.less @@ -100,5 +100,14 @@ } } +.action-icons { + position: absolute; + top: 15px; + right: 24px; + display: flex; + align-items: center; + min-width: 80px; + justify-content: space-between; +} diff --git a/src/pages/ServiceDashboard/Detail/index.tsx b/src/pages/ServiceDashboard/Detail/index.tsx index b23ff0dd..9e796405 100644 --- a/src/pages/ServiceDashboard/Detail/index.tsx +++ b/src/pages/ServiceDashboard/Detail/index.tsx @@ -1,7 +1,7 @@ import { Popover, Spin } from 'antd'; import React, { useEffect, useMemo, useState } from 'react'; import intl from 'react-intl-universal'; -import { RouteComponentProps, useLocation, withRouter } from 'react-router-dom'; +import { RouteComponentProps, useHistory, useLocation, withRouter } from 'react-router-dom'; import { connect } from 'react-redux'; import { Chart } from '@antv/g2'; import { @@ -11,6 +11,7 @@ import { getBaseLineByUnit, getMaxNum, getMetricsUniqName, + getMachineRouterPath, } from '@/utils/dashboard'; import { IDispatch, IRootState } from '@/store'; import { VALUE_TYPE } from '@/utils/promQL'; @@ -62,12 +63,14 @@ function ServiceDetail(props: IProps) { const [dataSources, setDataSources] = useState([]); const [showLoading, setShowLoading] = useState(false); + const history = useHistory(); + useEffect(() => { setShowLoading(loading && metricsFilterValues.frequency === 0) }, [loading, metricsFilterValues.frequency]); useEffect(() => { - const [ start, end ] = calcTimeRange(metricsFilterValues.timeRange); + const [start, end] = calcTimeRange(metricsFilterValues.timeRange); if (shouldCheckCluster()) { if (cluster?.id) { asyncGetSpaces({ @@ -164,8 +167,8 @@ function ServiceDetail(props: IProps) { } else { pollingData(); } - }, [metricsFilterValues.timeRange, metricsFilterValues.frequency, - metricsFilterValues.metricType, metricsFilterValues.period, metricsFilterValues.space, cluster, metricCharts]); + }, [metricsFilterValues.timeRange, metricsFilterValues.frequency, + metricsFilterValues.metricType, metricsFilterValues.period, metricsFilterValues.space, cluster, metricCharts]); useEffect(() => () => { clearPolling(); @@ -287,6 +290,17 @@ function ServiceDetail(props: IProps) { return curMetricOptions.find(item => item.metric === metricItem.metric) } + const getViewPath = (path: string): string => { + if (cluster?.id) { + return getMachineRouterPath(path, cluster.id); + } + return path; + } + + const handleViewDetail = (metricItem) => () => { + history.push(getViewPath(`/metrics-detail/${serviceType}d/${metricItem.metric.metric}`)); + } + return (
@@ -304,12 +318,11 @@ function ServiceDetail(props: IProps) {
{ metricCharts.map((metricChart, i) => ( -
+
{metricChart.metric.metric} {intl.get(`metric_description.${metricChart.metric.metric}`)}
} @@ -329,12 +342,20 @@ function ServiceDetail(props: IProps) { renderChart={renderChart(i)} />
-
- - {intl.get('common.baseLine')} +
+
+ +
+
+ + {intl.get('common.baseLine')} +
)) diff --git a/src/store/models/machine.ts b/src/store/models/machine.ts index 0e5b1203..6f97ffa4 100644 --- a/src/store/models/machine.ts +++ b/src/store/models/machine.ts @@ -51,7 +51,6 @@ export function MachineModelWrapper(service,) { ...state.metricsFilterValues, ...payload.metricsFilterValues } - debugger; return { ...state, metricsFilterValues diff --git a/src/utils/chart/chart.ts b/src/utils/chart/chart.ts index 51f34aa9..07d034d5 100644 --- a/src/utils/chart/chart.ts +++ b/src/utils/chart/chart.ts @@ -198,6 +198,8 @@ export const updateDetailChart = ( type: string; tickInterval: number; statSizes?: any; + maxNum?: number; + minNum?: number; }, ): Chart => { chartInstance.scale({ @@ -205,7 +207,16 @@ export const updateDetailChart = ( tickInterval: options.tickInterval, }, }); - + const { maxNum, minNum } = options + if (typeof maxNum === 'number' && typeof minNum === 'number' && maxNum >= 0 && minNum >= 0) { + chartInstance.scale({ + value: { + min: minNum || 0, + max: maxNum || 100, + tickInterval: +((maxNum - minNum) / 5).toFixed(2), + }, + }); + } return chartInstance; }; diff --git a/src/utils/dashboard.ts b/src/utils/dashboard.ts index 3bb9ce53..f3d94178 100644 --- a/src/utils/dashboard.ts +++ b/src/utils/dashboard.ts @@ -30,13 +30,13 @@ export const getProperStep = (start: number, end: number) => { } if (hours <= 12) { // 12hour - return 40; + return 120; } if (hours <= 24) { // 1 day - return 60; + return 240; } - return Math.ceil(hours / 24) * 60; + return Math.ceil(hours / 24) * 240; }; export const renderUnit = type => {