From fa58b37c809f16b1aa8e4c9d198b87b3751f66b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=B4=E6=99=96?= <2689991790@qq.com> Date: Mon, 4 Mar 2024 18:11:03 +0800 Subject: [PATCH 1/3] feat:Add monitor of tenant detail page --- ui/src/components/MonitorComp/LineGraph.tsx | 21 +-- ui/src/components/MonitorComp/index.tsx | 29 +++- .../MonitorDetail}/DataFilter.tsx | 127 ++++++++-------- ui/src/components/MonitorDetail/helper.ts | 53 +++++++ .../MonitorDetail}/index.less | 0 ui/src/components/MonitorDetail/index.tsx | 140 ++++++++++++++++++ ui/src/pages/Cluster/Detail/Monitor/helper.ts | 47 ------ ui/src/pages/Cluster/Detail/Monitor/index.tsx | 137 +++-------------- .../pages/Cluster/Detail/Overview/helper.ts | 10 +- ui/src/pages/Cluster/Detail/Tenant/index.tsx | 16 +- ui/src/pages/Cluster/index.tsx | 3 +- ui/src/pages/Tenant/Detail/Monitor/index.tsx | 54 ++++++- ui/src/pages/Tenant/Detail/index.tsx | 10 +- ui/src/pages/Tenant/TenantsList.tsx | 2 +- ui/src/pages/Tenant/index.tsx | 7 +- ui/src/services/index.ts | 28 +++- ui/src/services/typings.d.ts | 7 +- 17 files changed, 422 insertions(+), 269 deletions(-) rename ui/src/{pages/Cluster/Detail/Monitor => components/MonitorDetail}/DataFilter.tsx (80%) create mode 100644 ui/src/components/MonitorDetail/helper.ts rename ui/src/{pages/Cluster/Detail/Monitor => components/MonitorDetail}/index.less (100%) create mode 100644 ui/src/components/MonitorDetail/index.tsx delete mode 100644 ui/src/pages/Cluster/Detail/Monitor/helper.ts diff --git a/ui/src/components/MonitorComp/LineGraph.tsx b/ui/src/components/MonitorComp/LineGraph.tsx index bdc91d9b9..ca14829d7 100644 --- a/ui/src/components/MonitorComp/LineGraph.tsx +++ b/ui/src/components/MonitorComp/LineGraph.tsx @@ -1,7 +1,6 @@ import { POINT_NUMBER } from '@/constants'; import { useRequestOfMonitor } from '@/hook/useRequestOfMonitor'; -import type { QueryRangeType } from '@/pages/Cluster/Detail/Monitor'; -import { getNSName } from '@/pages/Cluster/Detail/Overview/helper'; +import type { QueryRangeType } from '@/components/MonitorDetail'; import { queryMetricsReq } from '@/services'; import { Line } from '@antv/g2plot'; import { useInViewport, useUpdateEffect } from 'ahooks'; @@ -22,9 +21,11 @@ export interface LineGraphProps { metrics: MetricType[]; labels: API.MetricsLabels; queryRange: QueryRangeType; + groupLabels:API.LableKeys[]; height?: number; isRefresh?: boolean; - type?: API.MonitorUserFor; + type?: API.MonitorUseTarget; + useFor: API.MonitorUseFor; } export default function LineGraph({ @@ -32,11 +33,12 @@ export default function LineGraph({ metrics, labels, queryRange, + groupLabels, height = 186, isRefresh = false, type = 'DETAIL', + useFor }: LineGraphProps) { - const [, chooseClusterName] = getNSName(); const [isEmpty, setIsEmpty] = useState(true); const [isloading, setIsloading] = useState(true); const lineGraphRef = useRef(null); @@ -44,12 +46,10 @@ export default function LineGraph({ const [inViewport] = useInViewport(lineGraphRef); // 进入可见区域次数,只在第一次进入可见区域发起网络请求 const [inViewportCount, setInViewportCount] = useState(0); - const groupLabels = _.uniq(labels.map((label) => label.key)); - const getQueryParms = () => { let metricsKeys: string[] = [metrics[0].key], realLabels = labels; - if (chooseClusterName) { + if (type === 'DETAIL') { metricsKeys = metrics.map((metric: MetricType) => metric.key); } if (type === 'OVERVIEW') realLabels = []; @@ -58,7 +58,8 @@ export default function LineGraph({ labels: realLabels, //为空则查询全部集群 metrics: metricsKeys, queryRange, - type + type, + useFor }; }; @@ -67,7 +68,7 @@ export default function LineGraph({ for (let metric of metricsData) { values.push(metric.value); } - + const config = { data: metricsData, xField: 'date', @@ -157,6 +158,8 @@ export default function LineGraph({ //开启实时模式后处理 useUpdateEffect(() => { + console.log('isRefresh',isRefresh); + if (!isRefresh) { if (inViewport) { queryMetrics(getQueryParms()); diff --git a/ui/src/components/MonitorComp/index.tsx b/ui/src/components/MonitorComp/index.tsx index b133300e5..8b11cf744 100644 --- a/ui/src/components/MonitorComp/index.tsx +++ b/ui/src/components/MonitorComp/index.tsx @@ -1,12 +1,12 @@ import { QuestionCircleOutlined } from '@ant-design/icons'; import { ProCard } from '@ant-design/pro-components'; import { useRequest } from 'ahooks'; -import { Card, Col, Row, Tooltip } from 'antd'; +import { Card,Col,Row,Tooltip } from 'antd'; import { useState } from 'react'; -import type { QueryRangeType } from '@/pages/Cluster/Detail/Monitor'; +import type { QueryRangeType } from '@/components/MonitorDetail'; import { getAllMetrics } from '@/services'; -import LineGraph, { LineGraphProps, MetricType } from './LineGraph'; +import LineGraph,{ LineGraphProps,MetricType } from './LineGraph'; import styles from './index.less'; //查询的label @@ -22,7 +22,9 @@ interface MonitorCompProps { queryRange: QueryRangeType; isRefresh?: boolean; queryScope:API.EventObjectType; - type: API.MonitorUserFor; + type: API.MonitorUseTarget; + groupLabels:API.LableKeys[]; + useFor?: API.MonitorUseFor; } export default function MonitorComp({ @@ -30,14 +32,15 @@ export default function MonitorComp({ queryRange, isRefresh = false, type, - queryScope + queryScope, + groupLabels, + useFor='cluster' }: MonitorCompProps) { const { data: allMetrics } = useRequest(getAllMetrics, { defaultParams: [queryScope], }); const [visible, setVisible] = useState(false); const [modalProps, setModalProps] = useState({}); - const Title = ({ metrics, name, @@ -78,7 +81,7 @@ export default function MonitorComp({
- {type === 'overview' ? ( + {type === 'OVERVIEW' ? (

{container.name}

) : (

{container.name}

@@ -90,7 +93,15 @@ export default function MonitorComp({
- {graphContainer.name} + {graphContainer.name}( + {graphContainer.metrics[0].unit} + {graphContainer.metrics[0].unit && + type === 'OVERVIEW' && + ','} + {type === 'OVERVIEW' + ? `${graphContainer.metrics[0].key}` + : ''} + ) {/* ), diff --git a/ui/src/pages/Cluster/Detail/Monitor/DataFilter.tsx b/ui/src/components/MonitorDetail/DataFilter.tsx similarity index 80% rename from ui/src/pages/Cluster/Detail/Monitor/DataFilter.tsx rename to ui/src/components/MonitorDetail/DataFilter.tsx index d3057a58b..4883faa2b 100644 --- a/ui/src/pages/Cluster/Detail/Monitor/DataFilter.tsx +++ b/ui/src/components/MonitorDetail/DataFilter.tsx @@ -1,20 +1,20 @@ -import { POINT_NUMBER, REFRESH_FREQUENCY } from '@/constants'; +import { POINT_NUMBER,REFRESH_FREQUENCY } from '@/constants'; import { intl } from '@/utils/intl'; import { ProCard } from '@ant-design/pro-components'; import { useUpdateEffect } from 'ahooks'; -import { Col, DatePicker, Row, Select, Switch } from 'antd'; +import { Col,DatePicker,Row,Select,Switch } from 'antd'; import type { RangePickerProps } from 'antd/es/date-picker'; import type { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; import moment from 'moment'; -import { useEffect, useState } from 'react'; +import { useEffect,useState } from 'react'; import { caculateStep } from './helper'; import type { - FilterDataType, - Label, - LabelType, - OptionType, - QueryRangeType, +FilterDataType, +Label, +LabelType, +OptionType, +QueryRangeType, } from './index'; import styles from './index.less'; @@ -26,7 +26,7 @@ interface DataFilterProps { queryRange: QueryRangeType; setQueryRange: React.Dispatch>; setIsRefresh: React.Dispatch>; - setFilterLable: React.Dispatch>; + setFilterLabel: React.Dispatch>; setFilterData: React.Dispatch>; } const { RangePicker } = DatePicker; @@ -91,8 +91,6 @@ const DateSelectOption: OptionType[] = [ type RangeValue = [Dayjs | null, Dayjs | null] | null; -//选择时间下拉框改变右边时间选择框 -//所以右边时间选择框需要value属性 受控 export default function DataFilter({ isRefresh, realTime, @@ -100,7 +98,7 @@ export default function DataFilter({ filterLabel, //发送请求的label queryRange, //defaultVAlue setIsRefresh, - setFilterLable, + setFilterLabel, setQueryRange, }: DataFilterProps) { const [zoneOption, setZoneOption] = useState([]); @@ -190,10 +188,10 @@ export default function DataFilter({ let isClear: boolean = !Boolean(val), currentLable = [...filterLabel]; if (isClear) { - //清空obzone&svr_ip + //clear obzone&svr_ip currentLable = clearLabel(clearLabel(filterLabel, 'obzone'), 'svr_ip'); } else { - //更新zone后清空server + //clear the server after updating the zone currentLable = clearLabel( updateLable(filterLabel, 'obzone', val!), 'svr_ip', @@ -205,16 +203,18 @@ export default function DataFilter({ const zoneSelectChange = (val: string | undefined) => { setSelectZone(val); setSelectServer(undefined); - setFilterLable(handleLabel(val)); - //清空 - if (typeof val === 'undefined') { - setServerOption(filterData.serverList); - return; + setFilterLabel(handleLabel(val)); + //clear + if(filterData.serverList){ + if (typeof val === 'undefined') { + setServerOption(filterData.serverList); + return; + } + const filterServers = filterData.serverList.filter((server: OptionType) => { + return server.zone === val; + }); + setServerOption(filterServers); } - const filterServers = filterData.serverList.filter((server: OptionType) => { - return server.zone === val; - }); - setServerOption(filterServers); }; const serverSelectChange = (val: string | undefined) => { @@ -225,7 +225,7 @@ export default function DataFilter({ } else { lable = updateLable(lable, 'svr_ip', val!); } - setFilterLable(lable); + setFilterLabel(lable); setSelectServer(val); }; @@ -276,10 +276,10 @@ export default function DataFilter({ }; useUpdateEffect(() => { - if (filterData.zoneList.length) { + if (filterData?.zoneList?.length) { setZoneOption(filterData.zoneList); } - if (filterData.serverList.length) { + if (filterData?.serverList?.length) { setServerOption(filterData.serverList); } }, [filterData]); @@ -319,41 +319,46 @@ export default function DataFilter({ extra={} > - -
- Zone: - {' '} -
- + {filterData.zoneList && ( + +
+ Zone: + {' '} +
+ + )}
{ + let serverList: OptionType[] = [], + zoneList: OptionType[] = [], + res = {}; + if (detail.servers) { + for (let server of detail.servers) { + if (server.address) + serverList.push({ + label: server.address, + value: server.address, + zone: server.zone, + }); + } + if (serverList.length) { + res.serverList = serverList; + } + } + console.log('detail',detail) + if (detail.zones || detail.replicas) { + for (let zone of detail.zones || detail.replicas) { + if (zone.zone) + zoneList.push({ + label: zone.zone, + value: zone.zone, + }); + } + if (zoneList.length) { + res.zoneList = zoneList; + } + } + + return { + ...res, + date: '', + }; +}; + +/** + * step: the interval between each point unit:s for example: half an hour 1800s, interval 30s, return sixty points + * @param pointNumber points,default 15 + * @param startTimeStamp unit:s + * @param endTimeStamp + * @returns + */ +export const caculateStep = ( + startTimeStamp: number, + endTimeStamp: number, + pointNumber: number, +): number => { + return Math.ceil((endTimeStamp - startTimeStamp) / pointNumber); +}; diff --git a/ui/src/pages/Cluster/Detail/Monitor/index.less b/ui/src/components/MonitorDetail/index.less similarity index 100% rename from ui/src/pages/Cluster/Detail/Monitor/index.less rename to ui/src/components/MonitorDetail/index.less diff --git a/ui/src/components/MonitorDetail/index.tsx b/ui/src/components/MonitorDetail/index.tsx new file mode 100644 index 000000000..ae064dbb2 --- /dev/null +++ b/ui/src/components/MonitorDetail/index.tsx @@ -0,0 +1,140 @@ +import { useUpdateEffect } from 'ahooks'; +import moment from 'moment'; +import { useEffect, useRef, useState } from 'react'; + +import MonitorComp from '@/components/MonitorComp'; +import { REFRESH_FREQUENCY } from '@/constants'; +import DataFilter from './DataFilter'; + +export type Label = + | 'ob_cluster_name' + | 'ob_cluster_id' + | 'tenant_name' + | 'tenant_id' + | 'svr_ip' + | 'obzone'; + +export type LabelType = { + key: Label; + value: string; +}; + +export type OptionType = { + label: string; + value: string | number; + zone?: string; +}; + +export type FilterDataType = { + zoneList?: OptionType[]; + serverList?: OptionType[]; + date?: any; +}; + +export type QueryRangeType = { + endTimestamp: number; + startTimestamp: number; + step: number; +}; + +interface MonitorDetailProps { + filterData:FilterDataType; + setFilterData:React.Dispatch>; + basicInfo:JSX.Element | null | undefined; + filterLabel:LabelType[]; + setFilterLabel:React.Dispatch>; + groupLabels:API.LableKeys[]; + queryScope:API.EventObjectType; +} + +const getDate = () => { + return moment + .unix(Math.ceil(new Date().valueOf() / 1000)) + .format('YYYY-MM-DD HH:mm:ss'); +}; + +const defaultQueryRange = { + step: 20, + endTimestamp: Math.floor(new Date().valueOf() / 1000), + startTimestamp: Math.floor(new Date().valueOf() / 1000) - 60 * 30, +}; + +//Query is somewhat similar to sql statement, label is equivalent to filter condition,for example: where label1=xxx and label2 = xxx +export default function MonitorDetail({ + filterData, + setFilterData, + filterLabel, + setFilterLabel, + basicInfo, + groupLabels, + queryScope +}:MonitorDetailProps) { + const [isRefresh, setIsRefresh] = useState(false); + const [realTime, setRealTime] = useState(getDate()); + const timerRef = useRef(); + const updateTimer = useRef(); + const [queryRange, setQueryRange] = + useState(defaultQueryRange); + const newQueryRangeRef = useRef(); //Only used to solve the problem of not getting the latest value in interval + + useUpdateEffect(() => { + if (isRefresh && !timerRef.current) { + timerRef.current = setInterval(() => { + let target; + if (!newQueryRangeRef.current) { + target = { ...queryRange }; + } else { + target = { ...newQueryRangeRef.current }; + } + newQueryRangeRef.current = { + step: target.step, + startTimestamp: target.startTimestamp + REFRESH_FREQUENCY, + endTimestamp: target.endTimestamp + REFRESH_FREQUENCY, + }; + setQueryRange(newQueryRangeRef.current); + }, REFRESH_FREQUENCY * 1000); + } + if (!isRefresh) { + clearInterval(timerRef.current); + } + + return () => { + if (timerRef.current) clearInterval(timerRef.current); + }; + }, [isRefresh]); + + //Real time update time + useEffect(() => { + updateTimer.current = setInterval(() => { + setRealTime(getDate()); + }, REFRESH_FREQUENCY * 1000); + return () => { + if (updateTimer.current) clearInterval(updateTimer.current); + }; + }, [isRefresh]); + + return ( +
+ { basicInfo } + + +
+ ); +} diff --git a/ui/src/pages/Cluster/Detail/Monitor/helper.ts b/ui/src/pages/Cluster/Detail/Monitor/helper.ts deleted file mode 100644 index acffc794e..000000000 --- a/ui/src/pages/Cluster/Detail/Monitor/helper.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { FilterDataType, OptionType } from '.'; - -export const getFilterData = (clusterDetail: any): FilterDataType => { - let serverList: OptionType[] = [], - zoneList: OptionType[] = []; - if (clusterDetail.servers) { - for (let server of clusterDetail.servers) { - if (server.address) - serverList.push({ - label: server.address, - value: server.address, - zone: server.zone, - }); - } - } - - if (clusterDetail.zones) { - for (let zone of clusterDetail.zones) { - if (zone.zone) - zoneList.push({ - label: zone.zone, - value: zone.zone, - }); - } - } - - return { - serverList, - zoneList, - date: '', - }; -}; - -/** - * step: 每个点位间隔时间s 例如 半小时1800s 间隔step 30s 返回60个点位 - * @param pointNumber 点数,默认15个点位 - * @param startTimeStamp 开始时间戳 精确到s - * @param endTimeStamp 结束时间戳 精确到s - * @returns - */ -export const caculateStep = ( - startTimeStamp: number, - endTimeStamp: number, - pointNumber: number, -): number => { - return Math.ceil((endTimeStamp - startTimeStamp) / pointNumber); -}; diff --git a/ui/src/pages/Cluster/Detail/Monitor/index.tsx b/ui/src/pages/Cluster/Detail/Monitor/index.tsx index df54277f7..d62f5ebcc 100644 --- a/ui/src/pages/Cluster/Detail/Monitor/index.tsx +++ b/ui/src/pages/Cluster/Detail/Monitor/index.tsx @@ -1,74 +1,22 @@ -import { useRequest, useUpdateEffect } from 'ahooks'; -import moment from 'moment'; -import { useEffect, useRef, useState } from 'react'; - -import MonitorComp from '@/components/MonitorComp'; -import { REFRESH_FREQUENCY } from '@/constants'; +import MonitorDetail from '@/components/MonitorDetail'; import { getClusterDetailReq } from '@/services'; +import { useRequest } from 'ahooks'; +import { useEffect, useState } from 'react'; import BasicInfo from '../Overview/BasicInfo'; import { getNSName } from '../Overview/helper'; -import DataFilter from './DataFilter'; -import { getFilterData } from './helper'; - -export type Label = - | 'ob_cluster_name' - | 'ob_cluster_id' - | 'tenant_name' - | 'tenant_id' - | 'svr_ip' - | 'obzone'; - -export type LabelType = { - key: Label; - value: string; -}; - -export type OptionType = { - label: string; - value: string | number; - zone?: string; -}; - -export type FilterDataType = { - zoneList: OptionType[]; - serverList: OptionType[]; - date: any; -}; - -export type QueryRangeType = { - endTimestamp: number; - startTimestamp: number; - step: number; //每个点位间隔时间s 例如 半小时1800s 间隔step 30s 返回60个点位 -}; +import type { FilterDataType,LabelType } from '@/components/MonitorDetail'; -const getDate = () => { - return moment - .unix(Math.ceil(new Date().valueOf() / 1000)) - .format('YYYY-MM-DD HH:mm:ss'); -}; +import { getFilterData } from '@/components/MonitorDetail/helper'; -const defaultQueryRange = { - step: 20, - endTimestamp: Math.floor(new Date().valueOf() / 1000), - startTimestamp: Math.floor(new Date().valueOf() / 1000) - 60 * 30, -}; -//查询和sql语句有些类似,label相当于过滤条件,就是where label1=xxx and label2 = xxx export default function Monitor() { const [[ns, name, clusterName]] = useState(getNSName()); - const [isRefresh, setIsRefresh] = useState(false); - const [realTime, setRealTime] = useState(getDate()); - const timerRef = useRef(); - const updateTimer = useRef(); - const [queryRange, setQueryRange] = - useState(defaultQueryRange); - const newQueryRangeRef = useRef(); //仅用于解决interval中拿不到最新值的问题 const [filterData, setFilterData] = useState({ zoneList: [], serverList: [], date: '', }); - const [filterLabel, setFilterLable] = useState([ + const [filterLabel, setFilterLabel] = useState([ { key: 'ob_cluster_name', value: clusterName, @@ -86,69 +34,22 @@ export default function Monitor() { }, ); - useUpdateEffect(() => { - if (isRefresh && !timerRef.current) { - timerRef.current = setInterval(() => { - let target; - if (!newQueryRangeRef.current) { - target = { ...queryRange }; - } else { - target = { ...newQueryRangeRef.current }; - } - newQueryRangeRef.current = { - step: target.step, - startTimestamp: target.startTimestamp + REFRESH_FREQUENCY, - endTimestamp: target.endTimestamp + REFRESH_FREQUENCY, - }; - setQueryRange(newQueryRangeRef.current); - }, REFRESH_FREQUENCY * 1000); - } - if (!isRefresh) { - clearInterval(timerRef.current); - } - - return () => { - if (timerRef.current) clearInterval(timerRef.current); - }; - }, [isRefresh]); - - //实时更新时间 - useEffect(() => { - updateTimer.current = setInterval(() => { - setRealTime(getDate()); - }, REFRESH_FREQUENCY * 1000); - return () => { - if (updateTimer.current) clearInterval(updateTimer.current); - }; - }, [isRefresh]); - useEffect(() => { getClusterDetail({ ns, name }); }, []); - return ( -
- {clusterDetail && ( - - )} - - -
+ + ) + } + /> ); } diff --git a/ui/src/pages/Cluster/Detail/Overview/helper.ts b/ui/src/pages/Cluster/Detail/Overview/helper.ts index aec9fee47..af2ad8a8f 100644 --- a/ui/src/pages/Cluster/Detail/Overview/helper.ts +++ b/ui/src/pages/Cluster/Detail/Overview/helper.ts @@ -1,7 +1,7 @@ -// 与UI无关函数 +// Functions without UI /** - * 通过url的path获取namespace和name + * Get the namespace, name and cluster name or tenant name through the path of the url * * @returns {Array} [namespace,name] * @example /cluster/ns=oceanbase&nm=test/overview => [oceanbase,test] @@ -13,11 +13,11 @@ const getNSName = () => { for (let path of pathArr) { if (path.split('&').length === 3) { - const [ns, name,clusterName] = path.split('&'); + const [ns, name,clusterOrTenantName] = path.split('&'); if (ns.split('=')[0] === 'ns' && name.split('=')[0] === 'nm') { res[0] = ns.split('=')[1]; res[1] = name.split('=')[1]; - res[2] = clusterName.split('=')[1]; + res[2] = clusterOrTenantName.split('=')[1]; } return res; }else if(path.split('&').length === 2){ @@ -32,7 +32,7 @@ const getNSName = () => { return res; }; -// 存在集群|zone|server状态不为running 则返回status为operating +// if there is cluster|zone|server whose status isn't running,the return status is operating. const formatClusterData = (responseData: any): API.ClusterDetail => { const res: any = { info: {}, diff --git a/ui/src/pages/Cluster/Detail/Tenant/index.tsx b/ui/src/pages/Cluster/Detail/Tenant/index.tsx index 2dee7f693..2f08090f7 100644 --- a/ui/src/pages/Cluster/Detail/Tenant/index.tsx +++ b/ui/src/pages/Cluster/Detail/Tenant/index.tsx @@ -46,12 +46,16 @@ export default function Tenant() { /> )} - + {tenantsList && ( + + )} ); diff --git a/ui/src/pages/Cluster/index.tsx b/ui/src/pages/Cluster/index.tsx index fd24458c9..9732c5f60 100644 --- a/ui/src/pages/Cluster/index.tsx +++ b/ui/src/pages/Cluster/index.tsx @@ -9,7 +9,7 @@ import MonitorComp from '@/components/MonitorComp'; import ClusterList from './ClusterList'; // import Monitor from './Monitor'; import { getObclusterListReq } from '@/services'; -import type { LabelType, QueryRangeType } from './Detail/Monitor'; +import type { LabelType, QueryRangeType } from '../../components/MonitorDetail'; const defaultQueryRange:QueryRangeType = { step: 20, @@ -47,6 +47,7 @@ const ClusterPage: React.FC = () => { filterLabel={clusterNames} queryScope='OBCLUSTER_OVERVIEW' type='OVERVIEW' + groupLabels={['ob_cluster_name']} queryRange={defaultQueryRange}/> ); diff --git a/ui/src/pages/Tenant/Detail/Monitor/index.tsx b/ui/src/pages/Tenant/Detail/Monitor/index.tsx index 44b302988..34aea6245 100644 --- a/ui/src/pages/Tenant/Detail/Monitor/index.tsx +++ b/ui/src/pages/Tenant/Detail/Monitor/index.tsx @@ -1,3 +1,55 @@ +import type { FilterDataType,LabelType } from '@/components/MonitorDetail'; +import MonitorDetail from '@/components/MonitorDetail'; +import { getNSName } from '@/pages/Cluster/Detail/Overview/helper'; +import { getTenant } from '@/services/tenant'; +import { useRequest } from 'ahooks'; +import { useEffect,useState } from 'react'; +import BasicInfo from '../Overview/BasicInfo'; + +import { getFilterData } from '@/components/MonitorDetail/helper'; + + export default function Monitor() { - return

Monitor

; + const [[ns, name, tenantName]] = useState(getNSName()); + const [filterLabel, setFilterLabel] = useState([ + { + key: 'tenant_name', + value: tenantName, + }, + ]); + const [filterData, setFilterData] = useState({ + zoneList: [], + date: '', + }); + const { data: tenantDetailResponse, run: getTenantDetail } = useRequest( + getTenant, + { + manual: true, + onSuccess: ({ data, successful }) => { + if (successful && data) { + setFilterData(getFilterData(data)); + } + }, + }, + ); + + useEffect(() => { + getTenantDetail({ ns, name }); + }, []); + const tenantDetail = tenantDetailResponse?.data; + return ( + + ) + } + /> + ); } diff --git a/ui/src/pages/Tenant/Detail/index.tsx b/ui/src/pages/Tenant/Detail/index.tsx index 4d914a17c..61ce9c4f5 100644 --- a/ui/src/pages/Tenant/Detail/index.tsx +++ b/ui/src/pages/Tenant/Detail/index.tsx @@ -72,11 +72,11 @@ const TenantDetail: React.FC = () => { }), link: `/tenant/${tenantId}/backup`, }, - // { - // title: '性能监控', - // key: 'monitor', - // link: `/tenant/${clusterId}/monitor`, - // }, + { + title: '性能监控', + key: 'monitor', + link: `/tenant/${tenantId}/monitor`, + }, ]; const userMenu = ( = [ dataIndex: 'name', key: 'name', render: (value, record) => ( - {value} + {value} ), }, { diff --git a/ui/src/pages/Tenant/index.tsx b/ui/src/pages/Tenant/index.tsx index 135851c43..7c46c81db 100644 --- a/ui/src/pages/Tenant/index.tsx +++ b/ui/src/pages/Tenant/index.tsx @@ -10,7 +10,7 @@ import { getAllTenants } from '@/services/tenant'; import TenantsList from './TenantsList'; -import type { LabelType, QueryRangeType } from '../Cluster/Detail/Monitor'; +import type { LabelType, QueryRangeType } from '../../components/MonitorDetail'; const defaultQueryRange: QueryRangeType = { step: 20, @@ -32,7 +32,8 @@ export default function TenantPage() { } }); const handleAddCluster = () => navigate('new'); - const tenantsList = tenantsListResponse?.data + const tenantsList = tenantsListResponse?.data; + return ( @@ -49,6 +50,8 @@ export default function TenantPage() { filterLabel={filterLabel} queryScope="OBTENANT" type="OVERVIEW" + useFor='tenant' + groupLabels={['tenant_name','ob_cluster_name']} queryRange={defaultQueryRange} /> diff --git a/ui/src/services/index.ts b/ui/src/services/index.ts index d852e1b03..aa51c5e0c 100644 --- a/ui/src/services/index.ts +++ b/ui/src/services/index.ts @@ -388,8 +388,17 @@ export async function getAllMetrics(type: API.EventObjectType) { return r.data; } -// //时间戳换算成时间 -export async function queryMetricsReq({ type, ...data }: API.QueryMetricsType) { +const setMetricNameFromLabels = (labels:API.MetricsLabels)=>{ + let tenantName = labels.find((label) => label.key === 'tenant_name')?.value; + let clustetName = labels + .filter((label) => label.key === 'ob_cluster_name') + .map((label) => label.value) + .join(','); + + return `${tenantName}(${clustetName})` +} + +export async function queryMetricsReq({ useFor, type, ...data }: API.QueryMetricsType) { const r = await request('/api/v1/metrics/query', { method: 'POST', data, @@ -397,11 +406,24 @@ export async function queryMetricsReq({ type, ...data }: API.QueryMetricsType) { if (r.successful) { if (!r.data || !r.data.length) return []; r.data.forEach((metric) => { + metric.values.forEach((item) => { // item.date = moment.unix(item.timestamp).format('YYYY-MM-DD HH:mm:ss'); item.date = item.timestamp * 1000; if (type === 'OVERVIEW') { - item.name = metric.metric.labels[0]?.value || ''; + if (useFor === 'tenant') { + let metricLabels = metric.metric.labels; + if(metricLabels.length > 1){ + item.name = setMetricNameFromLabels(metricLabels); + }else{ + item.name = metricLabels[0]?.value || ''; + } + } else { + item.name = + metric.metric.labels.find( + (label) => label.key === 'ob_cluster_name', + ).value || ''; + } } else { item.name = metric.metric.name; } diff --git a/ui/src/services/typings.d.ts b/ui/src/services/typings.d.ts index 5045bef5e..b45f19558 100644 --- a/ui/src/services/typings.d.ts +++ b/ui/src/services/typings.d.ts @@ -116,12 +116,15 @@ declare namespace API { labels: MetricsLabels; metrics: string[]; queryRange: { endTimestamp: number; startTimestamp: number; step: number }; - type:MonitorUserFor; + type: MonitorUseTarget; + useFor: MonitorUseFor; }; + type MonitorUseFor = 'cluster' | 'tenant'; + type EventType = 'NORMAL' | 'WARNING'; - type MonitorUserFor = 'OVERVIEW' | 'DETAIL'; + type MonitorUseTarget = 'OVERVIEW' | 'DETAIL'; type EventObjectType = 'OBCLUSTER' | 'OBTENANT' | 'OBCLUSTER_OVERVIEW'; From ac2f2e2434b53fa5eebe6b4d11b3e31f3e470cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=B4=E6=99=96?= <2689991790@qq.com> Date: Mon, 4 Mar 2024 19:21:39 +0800 Subject: [PATCH 2/3] delete console --- ui/src/components/MonitorComp/LineGraph.tsx | 2 -- ui/src/components/MonitorDetail/helper.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/ui/src/components/MonitorComp/LineGraph.tsx b/ui/src/components/MonitorComp/LineGraph.tsx index ca14829d7..ab33b2a15 100644 --- a/ui/src/components/MonitorComp/LineGraph.tsx +++ b/ui/src/components/MonitorComp/LineGraph.tsx @@ -158,8 +158,6 @@ export default function LineGraph({ //开启实时模式后处理 useUpdateEffect(() => { - console.log('isRefresh',isRefresh); - if (!isRefresh) { if (inViewport) { queryMetrics(getQueryParms()); diff --git a/ui/src/components/MonitorDetail/helper.ts b/ui/src/components/MonitorDetail/helper.ts index 2ba755069..ebe0b7cd4 100644 --- a/ui/src/components/MonitorDetail/helper.ts +++ b/ui/src/components/MonitorDetail/helper.ts @@ -17,7 +17,6 @@ export const getFilterData = (detail: any): FilterDataType => { res.serverList = serverList; } } - console.log('detail',detail) if (detail.zones || detail.replicas) { for (let zone of detail.zones || detail.replicas) { if (zone.zone) From adb19cfa12597630f428d26103d0bcf546078fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=B4=E6=99=96?= <2689991790@qq.com> Date: Mon, 4 Mar 2024 19:51:10 +0800 Subject: [PATCH 3/3] complete text --- ui/src/components/MonitorComp/index.tsx | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/ui/src/components/MonitorComp/index.tsx b/ui/src/components/MonitorComp/index.tsx index 8b11cf744..0155e0e98 100644 --- a/ui/src/components/MonitorComp/index.tsx +++ b/ui/src/components/MonitorComp/index.tsx @@ -93,15 +93,22 @@ export default function MonitorComp({
- {graphContainer.name}( - {graphContainer.metrics[0].unit} + {graphContainer.name} {graphContainer.metrics[0].unit && - type === 'OVERVIEW' && - ','} - {type === 'OVERVIEW' - ? `${graphContainer.metrics[0].key}` - : ''} - ) + `( + ${graphContainer.metrics[0].unit} + ${ + (graphContainer.metrics[0].unit && type) === + 'OVERVIEW' + ? ',' + : '' + } + ${ + type === 'OVERVIEW' + ? graphContainer.metrics[0].key + : '' + } + )`} {/*