diff --git a/dinky-web/package.json b/dinky-web/package.json index e60ee862d1..54c10317d2 100644 --- a/dinky-web/package.json +++ b/dinky-web/package.json @@ -36,12 +36,13 @@ ], "dependencies": { "@andrewray/react-multi-split-pane": "^0.3.5", - "@ant-design/charts": "^1.4.3", + "@ant-design/charts": "^2.1.1", "@ant-design/icons": "^5.3.7", "@ant-design/pro-components": "^2.6.43", "@ant-design/pro-layout": "^7.17.16", "@ant-design/pro-table": "^3.13.11", "@ant-design/use-emotion-css": "^1.0.4", + "@antv/layout": "^0.3.25", "@antv/x6": "^2.15.6", "@antv/x6-plugin-selection": "^2.2.1", "@antv/x6-react-shape": "^2.2.2", @@ -65,9 +66,11 @@ "react-countup": "^6.5.0", "react-dom": "^18.0.0", "react-helmet-async": "^2.0.1", + "react-infinite-scroll-component": "^6.1.0", "react-lineage-dag": "^2.0.36", "react-markdown": "^9.0.1", "react-spring": "^9.7.3", + "react-fast-marquee": "^1.6.4", "react-use-cookie": "^1.4.0", "redux-persist": "^6.0.0", "remark-gfm": "^4.0.0", @@ -82,6 +85,7 @@ "@types/classnames": "^2.3.1", "@types/express": "^4.17.21", "@types/history": "^5.0.0", + "@types/js-cookie": "^3.0.6", "@types/lodash": "^4.14.199", "@types/react": "^18.2.39", "@types/react-dom": "^18.2.17", @@ -95,6 +99,7 @@ "lint-staged": "^13", "prettier": "^2", "react-dev-inspector": "^2.0.0", + "react-fast-marquee": "^1.6.4", "react-inspector": "^6.0.2", "sql-formatter": "^13.0.1", "swagger-ui-dist": "^5.10.3", diff --git a/dinky-web/src/components/Flink/FlinkChart/index.tsx b/dinky-web/src/components/Flink/FlinkChart/index.tsx index c1215c400e..9010e286e8 100644 --- a/dinky-web/src/components/Flink/FlinkChart/index.tsx +++ b/dinky-web/src/components/Flink/FlinkChart/index.tsx @@ -18,13 +18,14 @@ */ import { ChartData } from '@/pages/Metrics/JobMetricsList/data'; -import { differenceDays } from '@/utils/function'; -import { Line } from '@ant-design/charts'; +import { THEME } from '@/types/Public/data'; +import { differenceDays, getChartThemeColor } from '@/utils/function'; +import { Line, LineConfig } from '@ant-design/charts'; import { ExpandOutlined } from '@ant-design/icons'; import { ProCard, StatisticCard } from '@ant-design/pro-components'; import { Col, Modal, Radio, Segmented, Space } from 'antd'; import Paragraph from 'antd/es/typography/Paragraph'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; type FlinkChartProps = { title: string; @@ -51,6 +52,7 @@ const FlinkChart = (props: FlinkChartProps) => { titleWidth: '60%' }); + const [themeColor, setThemeColor] = useState(THEME.CHART_THEME_LIGHT); const [showExtra, setShowExtra] = useState(false); const getLineTimeMask = (charData: ChartData[]) => { @@ -69,16 +71,23 @@ const FlinkChart = (props: FlinkChartProps) => { } }; - const config = { - animation: false, + useEffect(() => { + setThemeColor(getChartThemeColor()); + }, [getChartThemeColor()]); + + const config: LineConfig = { + animation: true, + autoFit: true, data: data, smooth: true, - xField: 'time', + theme: themeColor, yField: 'value', - xAxis: { - type: 'time', - mask: getLineTimeMask(data), - tickCount: 40 + xField: (d: ChartData) => new Date(d.time), + axis: { + x: { + tickCount: 10, + mask: getLineTimeMask(data) + } }, ...chartOptions }; @@ -163,7 +172,8 @@ const FlinkChart = (props: FlinkChartProps) => { ) : ( = { + data: T[]; + defaultPageSize: number; + layout: (data: T[]) => React.ReactNode; + filter?: FilterProp; +}; + +type FilterProp = { + content: (data: T[], setFilter: React.Dispatch) => React.ReactNode; + filter: (data: T, filter: F) => boolean; +}; + +const ListPagination = (props: ListPaginationProps) => { + const [data, setData] = useState(props.data); + const [currentData, setCurrentData] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const [currentPageSize, setCurrentPageSize] = useState(props.defaultPageSize); + const [filter, setFilter] = useState({} as F); + + useEffect(() => { + const newData = props.data.filter((item) => { + if (props.filter) { + return props.filter.filter(item, filter); + } + return true; + }); + setCurrentData( + newData.slice((currentPage - 1) * currentPageSize, currentPage * currentPageSize) + ); + setData(newData); + }, [currentPage, currentPageSize, filter, data]); + + return ( + <> + {props.filter && props.filter.content(props.data, setFilter)} + {props.layout(currentData)} + `${range[0]}-${range[1]} of ${total} Items`} + defaultPageSize={props.defaultPageSize} + responsive + showLessItems + defaultCurrent={currentPage} + onChange={(page, pageSize) => { + setCurrentPage(page); + setCurrentPageSize(pageSize); + }} + /> + + ); +}; + +export default ListPagination; diff --git a/dinky-web/src/components/MarqueeAlert/index.tsx b/dinky-web/src/components/MarqueeAlert/index.tsx new file mode 100644 index 0000000000..0b2887d6b5 --- /dev/null +++ b/dinky-web/src/components/MarqueeAlert/index.tsx @@ -0,0 +1,33 @@ +import {Alert, Result} from "antd"; +import Marquee from "react-fast-marquee"; +import React from "react"; + + +interface MetricsProps { + tips: string | React.ReactNode + type: 'success' | 'info' | 'warning' | 'error'; + showIcon?: boolean; + banner?: boolean; + play?: boolean; +} + + +/** + * The scrolling message prompt component is only used for long text, but its width may exceed the width of the container, so scrolling display is required + * @param props + */ +export default (props: MetricsProps) => { + + const {tips, type, banner = false, showIcon = true, play = true} = props; + + const renderMarquee = () => { + return <> + + {tips} + + + } + + + return <> +}; diff --git a/dinky-web/src/locales/en-US/pages.ts b/dinky-web/src/locales/en-US/pages.ts index fbd9956fc2..77e849b836 100644 --- a/dinky-web/src/locales/en-US/pages.ts +++ b/dinky-web/src/locales/en-US/pages.ts @@ -203,6 +203,8 @@ export default { 'devops.jobinfo.job.keyConfirm': '{key} this Job?', 'devops.jobinfo.metrics.configMetrics': 'Metrics Config', 'devops.jobinfo.metrics.metricsItems': 'Metrics Items', + 'devops.jobinfo.metrics.vertices': 'Vertices', + 'devops.jobinfo.metrics.name': 'Metrics Name', 'devops.jobinfo.metrics.selected': 'Selected', 'devops.jobinfo.offline': 'Offline', 'devops.jobinfo.recently.job.status': 'View recently saved job status information', @@ -359,7 +361,7 @@ export default { 'metrics.flink.subTask.placeholder': 'Please select a SubTask Name', 'metrics.flink.taskId': 'Dinky Job ID', 'metrics.dinky.not.open': - 'Dinky Server monitoring is not enabled, please go to the Setting Center -> Global Settings -> Metrics Configuration -> Dinky JVM Monitor switch to open', + 'The monitoring function is not enabled, and the Dinky Server and Flink task monitoring functions cannot be used. \nPlease go to the Configuration Center -> Global Configuration -> Metrics Configuration -> Dinky JVM Monitor switch to enable it', 'metrics.flink.deleteConfirm': 'Are you sure to delete the monitoring data under this task? \nAttention: This operation will synchronously affect the monitoring data of the operation and maintenance center for this task!! \nPlease operate with caution, this operation is irreversible!', diff --git a/dinky-web/src/locales/zh-CN/pages.ts b/dinky-web/src/locales/zh-CN/pages.ts index 35bfcf9b32..b9ce566f15 100644 --- a/dinky-web/src/locales/zh-CN/pages.ts +++ b/dinky-web/src/locales/zh-CN/pages.ts @@ -194,6 +194,8 @@ export default { 'devops.jobinfo.job.keyConfirm': '确定 {key} 该作业吗?', 'devops.jobinfo.metrics.configMetrics': '任务监控配置', 'devops.jobinfo.metrics.metricsItems': '监控项列表', + 'devops.jobinfo.metrics.vertices': '顶点', + 'devops.jobinfo.metrics.name': '监控项名称', 'devops.jobinfo.metrics.selected': '已选择', 'devops.jobinfo.offline': '下线', 'devops.jobinfo.recently.job.status': '查看最近保存的作业状态信息', @@ -346,7 +348,7 @@ export default { 'metrics.flink.subTask.placeholder': '请选择子任务名称', 'metrics.flink.taskId': 'Dinky 任务ID', 'metrics.dinky.not.open': - '暂未开启 Dinky Server 监控, 请前往 配置中心 -> 全局配置 -> Metrics 配置 -> Dinky JVM Monitor 开关 进行开启', + '未开启监控功能,Dinky Server 和 Flink 任务监控功能无法使用. \n请前往 配置中心 -> 全局配置 -> Metrics 配置 -> Dinky JVM Monitor 开关 进行开启', 'metrics.flink.deleteConfirm': '确认删除该任务下的监控数据吗? \n注意:该操作会同步影响运维中心该任务的监控数据!!\n请谨慎操作,该操作不可撤消!', /** diff --git a/dinky-web/src/pages/DataStudio/FooterContainer/index.tsx b/dinky-web/src/pages/DataStudio/FooterContainer/index.tsx index 7dcadf40e7..6651b1b46b 100644 --- a/dinky-web/src/pages/DataStudio/FooterContainer/index.tsx +++ b/dinky-web/src/pages/DataStudio/FooterContainer/index.tsx @@ -62,13 +62,16 @@ const FooterContainer: React.FC = (props) => { useEffect(() => { const eventSource = getSseData(API_CONSTANTS.BASE_URL + API_CONSTANTS.GET_JVM_INFO); eventSource.onmessage = (event) => { - const data = JSON.parse(event.data).data; - setMemDetailInfo( - Number(data['heapUsed'] / 1024 / 1024).toFixed(0) + - '/' + - Number(data['max'] / 1024 / 1024).toFixed(0) + - 'M' - ); + const respData = JSON.parse(event.data); + const data = respData.data; + if (respData['topic'] != 'HEART_BEAT') { + setMemDetailInfo( + Number(data['heapUsed'] / 1024 / 1024).toFixed(0) + + '/' + + Number(data['max'] / 1024 / 1024).toFixed(0) + + 'M' + ); + } }; return () => { eventSource.close(); diff --git a/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/JobChart/JobChart.tsx b/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/JobChart/JobChart.tsx index 7e722e4620..df5725ef31 100644 --- a/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/JobChart/JobChart.tsx +++ b/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/JobChart/JobChart.tsx @@ -18,15 +18,19 @@ */ import FlinkChart from '@/components/Flink/FlinkChart'; +import ListPagination from '@/components/Flink/ListPagination'; import useHookRequest from '@/hooks/useHookRequest'; import { SseData } from '@/models/Sse'; import { SSE_TOPIC } from '@/pages/DevOps/constants'; import { JobMetricsItem, MetricsTimeFilter } from '@/pages/DevOps/JobDetail/data'; -import { getMetricsData } from '@/pages/DevOps/JobDetail/srvice'; +import { getMetricsData } from '@/pages/DevOps/JobDetail/service'; +import { Filter, isBlank } from '@/pages/Metrics/JobMetricsList'; import { ChartData } from '@/pages/Metrics/JobMetricsList/data'; import { MetricsDataType } from '@/pages/Metrics/Server/data'; import { Jobs } from '@/types/DevOps/data'; -import { Empty, Row, Spin } from 'antd'; +import { l } from '@/utils/intl'; +import { ProFormSelect, ProFormText, QueryFilter } from '@ant-design/pro-components'; +import { Empty, Spin } from 'antd'; import { useEffect, useState } from 'react'; import { useModel } from 'umi'; @@ -44,7 +48,7 @@ const JobChart = (props: JobChartProps) => { subscribeTopic: model.subscribeTopic })); - const { loading } = useHookRequest(getMetricsData, { + const { loading, refresh: refreshMetricsData } = useHookRequest(getMetricsData, { defaultParams: [timeRange, jobDetail.instance.jid], refreshDeps: [timeRange, metricsList], onSuccess: (result) => { @@ -102,7 +106,62 @@ const JobChart = (props: JobChartProps) => { }; return ( - {renderMetricsCardList(metricsList ?? {}, chartDatas)} + {metricsList && ( + + data={metricsList} + layout={(data) => renderMetricsCardList(data, chartDatas)} + defaultPageSize={12} + filter={{ + content: (data: JobMetricsItem[], setFilter) => { + return ( + + labelWidth={'auto'} + span={8} + defaultCollapsed + split + onFinish={async (values) => setFilter(values)} + onReset={async () => { + // 清空筛选条件 + setFilter({ + vertices: '', + metrics: '' + }); + await refreshMetricsData(); + }} + > + item.vertices))].reduce( + (accumulator, item) => { + accumulator[item] = item; + return accumulator; + }, + {} as Record + )} + /> + + + ); + }, + filter: (item: JobMetricsItem, filter: Filter) => { + let rule = true; + if (!isBlank(filter.vertices)) { + rule = rule && item.vertices.includes(filter.vertices); + } + if (!isBlank(filter.metrics)) { + rule = rule && item.metrics.includes(filter.metrics); + } + return rule; + } + }} + /> + )} ); }; diff --git a/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/MetricsForm/MetricsConfigTab.tsx b/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/MetricsForm/MetricsConfigTab.tsx index 3d933e74f1..1e08c14d53 100644 --- a/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/MetricsForm/MetricsConfigTab.tsx +++ b/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/MetricsForm/MetricsConfigTab.tsx @@ -19,7 +19,7 @@ import useHookRequest from '@/hooks/useHookRequest'; import { JobMetricsItem } from '@/pages/DevOps/JobDetail/data'; -import { getFLinkVertices } from '@/pages/DevOps/JobDetail/srvice'; +import { getFLinkVertices } from '@/pages/DevOps/JobDetail/service'; import { Jobs } from '@/types/DevOps/data'; import { l } from '@/utils/intl'; import { Transfer } from 'antd'; diff --git a/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/function.tsx b/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/function.tsx index baa1bda544..87d34e68c7 100644 --- a/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/function.tsx +++ b/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/function.tsx @@ -17,7 +17,7 @@ * */ -import { JobMetricsItem } from '@/pages/DevOps/JobDetail/data'; +import { JobMetricsItem } from '@/pages/DevOps/JobDetail/data.d'; /** * Checks if a job status indicates that the job is done. @@ -25,7 +25,7 @@ import { JobMetricsItem } from '@/pages/DevOps/JobDetail/data'; * @returns {boolean} - True if the job status indicates that the job is done, false otherwise. * @param list */ -export function buildMetricsTarget(list?: JobMetricsItem[]) { +export function buildMetricsTarget(list: JobMetricsItem[] = []) { if (!list) return {}; const result: Record = {}; list.forEach((metrics) => { diff --git a/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/index.tsx b/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/index.tsx index c79f14f746..0d740a8abd 100644 --- a/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/index.tsx +++ b/dinky-web/src/pages/DevOps/JobDetail/JobMetrics/index.tsx @@ -19,17 +19,21 @@ import MetricsFilter from '@/components/Flink/MetricsFilter/MetricsFilter'; import useHookRequest from '@/hooks/useHookRequest'; -import { JOB_STATUS } from '@/pages/DevOps/constants'; -import { JobMetricsItem, JobProps, MetricsTimeFilter } from '@/pages/DevOps/JobDetail/data'; -import { buildMetricsTarget } from '@/pages/DevOps/JobDetail/JobMetrics/function'; +import {JOB_STATUS} from '@/pages/DevOps/constants'; +import {JobMetricsItem, JobProps, MetricsTimeFilter} from '@/pages/DevOps/JobDetail/data'; +import {buildMetricsTarget} from '@/pages/DevOps/JobDetail/JobMetrics/function'; import MonitorConfigForm from '@/pages/DevOps/JobDetail/JobMetrics/MetricsForm/MetricsConfigForm'; -import { getMetricsLayout, putMetricsLayout } from '@/pages/DevOps/JobDetail/srvice'; -import { Space } from 'antd'; -import { useState } from 'react'; +import {getMetricsLayout, putMetricsLayout} from '@/pages/DevOps/JobDetail/service'; +import {Result, Space} from 'antd'; +import React, {memo, useState} from 'react'; import JobChart from './JobChart/JobChart'; +import {SysConfigStateType} from "@/pages/SettingCenter/GlobalSetting/model"; +import {connect} from "@umijs/max"; +import MarqueeAlert from "@/components/MarqueeAlert"; +import {l} from "@/utils/intl"; -const JobMetrics = (props: JobProps) => { - const { jobDetail } = props; +const JobMetrics = (props: JobProps & connect) => { + const {jobDetail, dispatch, enableMetricMonitor} = props; const layoutName = `${jobDetail.instance.name}-${jobDetail.instance.taskId}`; const [timeRange, setTimeRange] = useState({ @@ -38,10 +42,14 @@ const JobMetrics = (props: JobProps) => { isReal: true }); - const layoutData = useHookRequest(getMetricsLayout, { defaultParams: [layoutName] }); + const layoutData = useHookRequest(getMetricsLayout, { + defaultParams: [layoutName], + refreshDeps: [layoutName, timeRange, jobDetail.instance] + }); const saveLayout = useHookRequest(putMetricsLayout, { manual: true, - defaultParams: [layoutName, []] + defaultParams: [layoutName, []], + refreshDeps: [layoutName] }); const onTimeSelectChange = (filter: MetricsTimeFilter) => { @@ -52,27 +60,38 @@ const JobMetrics = (props: JobProps) => { let params: JobMetricsItem[] = []; Object.values(targetKeys).forEach((i) => params.push(...i)); await saveLayout.run(layoutName, params); - layoutData.run(layoutName); + await layoutData.run(layoutName); + await layoutData.refresh(); return true; }; return ( <> - - - {jobDetail.instance.status == JOB_STATUS.RUNNING ? ( - - ) : ( - <> - )} - - + {enableMetricMonitor ? <> + + + {jobDetail.instance.status == JOB_STATUS.RUNNING ? ( + + ) : ( + <> + )} + + + : {l('metrics.dinky.not.open')}} />} ); }; -export default JobMetrics; +export default connect( + ({SysConfig}: { SysConfig: SysConfigStateType }) => ({ + enableMetricMonitor: SysConfig.enableMetricMonitor + }) +)(memo(JobMetrics)); diff --git a/dinky-web/src/pages/DevOps/JobDetail/index.tsx b/dinky-web/src/pages/DevOps/JobDetail/index.tsx index edb4aa9b08..9d2b6afd1e 100644 --- a/dinky-web/src/pages/DevOps/JobDetail/index.tsx +++ b/dinky-web/src/pages/DevOps/JobDetail/index.tsx @@ -28,7 +28,7 @@ import JobMetrics from '@/pages/DevOps/JobDetail/JobMetrics'; import JobOperator from '@/pages/DevOps/JobDetail/JobOperator/JobOperator'; import JobConfigTab from '@/pages/DevOps/JobDetail/JobOverview/JobOverview'; import JobVersionTab from '@/pages/DevOps/JobDetail/JobVersion/JobVersionTab'; -import { refeshJobInstance } from '@/pages/DevOps/JobDetail/srvice'; +import { refeshJobInstance } from '@/pages/DevOps/JobDetail/service'; import { Jobs } from '@/types/DevOps/data'; import { l } from '@/utils/intl'; import { history } from '@@/core/history'; diff --git a/dinky-web/src/pages/DevOps/JobDetail/srvice.ts b/dinky-web/src/pages/DevOps/JobDetail/service.ts similarity index 100% rename from dinky-web/src/pages/DevOps/JobDetail/srvice.ts rename to dinky-web/src/pages/DevOps/JobDetail/service.ts diff --git a/dinky-web/src/pages/Metrics/JobMetricsList/index.tsx b/dinky-web/src/pages/Metrics/JobMetricsList/index.tsx index e187740aea..621296e307 100644 --- a/dinky-web/src/pages/Metrics/JobMetricsList/index.tsx +++ b/dinky-web/src/pages/Metrics/JobMetricsList/index.tsx @@ -19,11 +19,12 @@ import { PopconfirmDeleteBtn } from '@/components/CallBackButton/PopconfirmDeleteBtn'; import FlinkChart from '@/components/Flink/FlinkChart'; +import ListPagination from '@/components/Flink/ListPagination'; import useHookRequest from '@/hooks/useHookRequest'; import { SseData } from '@/models/Sse'; import { SSE_TOPIC } from '@/pages/DevOps/constants'; import { JobMetricsItem, MetricsTimeFilter } from '@/pages/DevOps/JobDetail/data'; -import { getMetricsData } from '@/pages/DevOps/JobDetail/srvice'; +import { getMetricsData } from '@/pages/DevOps/JobDetail/service'; import { ChartData } from '@/pages/Metrics/JobMetricsList/data'; import { MetricsDataType } from '@/pages/Metrics/Server/data'; import { getMetricsLayout } from '@/pages/Metrics/service'; @@ -31,8 +32,8 @@ import { handleRemoveById } from '@/services/BusinessCrud'; import { API_CONSTANTS } from '@/services/endpoints'; import { l } from '@/utils/intl'; import { useModel } from '@@/exports'; -import { ProCard } from '@ant-design/pro-components'; -import { Empty, Row, Spin } from 'antd'; +import { ProCard, ProFormSelect, ProFormText, QueryFilter } from '@ant-design/pro-components'; +import { Empty, Spin } from 'antd'; import { useEffect, useState } from 'react'; export type MetricsProps = { @@ -45,7 +46,9 @@ const JobMetricsList = (props: MetricsProps) => { const [chartDatas, setChartDatas] = useState>({}); const [jobIds, setJobIds] = useState(''); - const { data, refresh } = useHookRequest(getMetricsLayout, { defaultParams: [] }); + const { data, refresh: refreshMetricsLayout } = useHookRequest(getMetricsLayout, { + defaultParams: [] + }); const dataProcess = (sourceData: Record, datas: MetricsDataType[]) => { datas.forEach((item) => { @@ -67,11 +70,14 @@ const JobMetricsList = (props: MetricsProps) => { return sourceData; }; - const { loading } = useHookRequest(getMetricsData, { - defaultParams: [timeRange, jobIds], - refreshDeps: [timeRange, jobIds], - onSuccess: (result: MetricsDataType[]) => setChartDatas(() => dataProcess({}, result)) - }); + const { loading, refresh: refreshMetricsData } = useHookRequest( + getMetricsData, + { + defaultParams: [timeRange, jobIds], + refreshDeps: [timeRange, jobIds], + onSuccess: (result: MetricsDataType[]) => setChartDatas(() => dataProcess({}, result)) + } + ); const { subscribeTopic } = useModel('Sse', (model: any) => ({ subscribeTopic: model.subscribeTopic @@ -96,7 +102,7 @@ const JobMetricsList = (props: MetricsProps) => { }, [data]); const renderFlinkChartGroup = (flinkJobId: string, metricsList: JobMetricsItem[]) => { - if (metricsList && metricsList.length > 0) { + if (metricsList && metricsList?.length > 0) { return metricsList?.map((item) => { const key = `${flinkJobId}-${item.vertices}-${item.metrics}`; return ( @@ -110,7 +116,7 @@ const JobMetricsList = (props: MetricsProps) => { ); }); } - return ; + return []; }; return ( @@ -126,13 +132,13 @@ const JobMetricsList = (props: MetricsProps) => { title={lo.layoutName} collapsible ghost - gutter={[0, 8]} + gutter={[8, 16]} subTitle={ { - await handleRemoveById(API_CONSTANTS.METRICS_LAYOUT_DELETE, lo.taskId, () => { - refresh(); - }); + await handleRemoveById(API_CONSTANTS.METRICS_LAYOUT_DELETE, lo.taskId, () => + refreshMetricsLayout() + ); }} description={ {l('metrics.flink.deleteConfirm')} @@ -140,15 +146,79 @@ const JobMetricsList = (props: MetricsProps) => { /> } > - {renderFlinkChartGroup(lo.flinkJobId, lo.metrics)} + + data={lo.metrics} + layout={(data1) => renderFlinkChartGroup(lo.flinkJobId, data1)} + defaultPageSize={12} + filter={{ + content: (data: JobMetricsItem[], setFilter) => { + return ( + + labelWidth={'auto'} + span={8} + defaultCollapsed + split + onFinish={async (values) => setFilter(values)} + onReset={async () => { + // 清空筛选条件 + setFilter({ + vertices: '', + metrics: '' + }); + await refreshMetricsData(); + await refreshMetricsLayout(); + }} + > + item.vertices))].reduce( + (accumulator, item) => { + accumulator[item] = item; + return accumulator; + }, + {} as Record + )} + /> + + + ); + }, + filter: (item: JobMetricsItem, filter: Filter) => { + let rule = true; + if (!isBlank(filter.vertices)) { + rule = rule && item.vertices.includes(filter.vertices); + } + if (!isBlank(filter.metrics)) { + rule = rule && item.metrics.includes(filter.metrics); + } + return rule; + } + }} + /> ); })} -
-
); }; +export type Filter = { + vertices: string; + metrics: string; +}; +export const isBlank = (str: string) => { + if (str) { + return false; + } else if (str == '') { + return true; + } + return true; +}; export default JobMetricsList; diff --git a/dinky-web/src/pages/Metrics/Server/CPU/index.tsx b/dinky-web/src/pages/Metrics/Server/CPU/index.tsx index d9b81f57d9..de00102b27 100644 --- a/dinky-web/src/pages/Metrics/Server/CPU/index.tsx +++ b/dinky-web/src/pages/Metrics/Server/CPU/index.tsx @@ -19,13 +19,12 @@ import { JVMMetric } from '@/pages/Metrics/Server/data'; import { Area, AreaConfig } from '@ant-design/plots'; -import { Datum } from '@antv/g2plot'; -import { AreaOptions as G2plotConfig } from '@antv/g2plot/lib/plots/area/types'; +import { Chart } from '@ant-design/plots/es/interface'; import React from 'react'; type CpuProps = { data: JVMMetric[]; - chartConfig: G2plotConfig; + chartConfig: Chart; }; type Cpu = { time: Date; @@ -41,13 +40,17 @@ const CPU: React.FC = (props) => { const config: AreaConfig = { ...chartConfig, data: dataList, - yAxis: { - min: 0, - max: 100 + axis: { + y: { + min: 0, + max: 100 + } }, tooltip: { - formatter: (datum: Datum) => { - return { name: 'Cpu Used', value: datum.value + ' %' }; + name: 'Cpu Used', + channel: 'y', + valueFormatter: (num: Number) => { + return num + ' %'; } } }; diff --git a/dinky-web/src/pages/Metrics/Server/Heap/index.tsx b/dinky-web/src/pages/Metrics/Server/Heap/index.tsx index 6610ce3781..db2167ddd3 100644 --- a/dinky-web/src/pages/Metrics/Server/Heap/index.tsx +++ b/dinky-web/src/pages/Metrics/Server/Heap/index.tsx @@ -19,14 +19,13 @@ import { JVMMetric } from '@/pages/Metrics/Server/data'; import { Area, AreaConfig } from '@ant-design/plots'; -import { Datum } from '@antv/g2plot'; -import { AreaOptions as G2plotConfig } from '@antv/g2plot/lib/plots/area/types'; +import { Chart } from '@ant-design/plots/es/interface'; import React from 'react'; type HeapProps = { data: JVMMetric[]; max: number; - chartConfig: G2plotConfig; + chartConfig: Chart; }; type Heap = { time: Date; @@ -44,13 +43,11 @@ const Heap: React.FC = (props) => { const config: AreaConfig = { ...chartConfig, data: dataList, - yAxis: { - min: 0, - max: max - }, tooltip: { - formatter: (datum: Datum) => { - return { name: 'Heap Memory', value: datum.value + ' MB' }; + name: 'Heap Memory', + channel: 'y', + valueFormat: (datum: Number) => { + return datum + ' MB'; } } }; diff --git a/dinky-web/src/pages/Metrics/Server/OutHeap/index.tsx b/dinky-web/src/pages/Metrics/Server/OutHeap/index.tsx index ad87be9689..519e4c9224 100644 --- a/dinky-web/src/pages/Metrics/Server/OutHeap/index.tsx +++ b/dinky-web/src/pages/Metrics/Server/OutHeap/index.tsx @@ -19,14 +19,13 @@ import { JVMMetric } from '@/pages/Metrics/Server/data'; import { Area, AreaConfig } from '@ant-design/plots'; -import { Datum } from '@antv/g2plot'; -import { AreaOptions as G2plotConfig } from '@antv/g2plot/lib/plots/area/types'; +import { Chart } from '@ant-design/plots/es/interface'; import React from 'react'; type NonHeapProps = { data: JVMMetric[]; max: number; - chartConfig: G2plotConfig; + chartConfig: Chart; }; type NonHeap = { time: Date; @@ -44,13 +43,17 @@ const NonHeap: React.FC = (props) => { const config: AreaConfig = { ...chartConfig, data: dataList, - yAxis: { - min: 0, - max: max + axis: { + y: { + min: 0, + max: max + } }, tooltip: { - formatter: (datum: Datum) => { - return { name: 'NonHeap Memory', value: datum.value + ' MB' }; + name: 'NonHeap Memory', + channel: 'y', + valueFormat: (datum: Number) => { + return datum + ' MB'; } } }; diff --git a/dinky-web/src/pages/Metrics/Server/Thread/index.tsx b/dinky-web/src/pages/Metrics/Server/Thread/index.tsx index 1ed70eacd3..bba5f2434c 100644 --- a/dinky-web/src/pages/Metrics/Server/Thread/index.tsx +++ b/dinky-web/src/pages/Metrics/Server/Thread/index.tsx @@ -19,12 +19,12 @@ import { JVMMetric } from '@/pages/Metrics/Server/data'; import { Area, AreaConfig } from '@ant-design/plots'; -import { AreaOptions as G2plotConfig } from '@antv/g2plot/lib/plots/area/types'; +import { Chart } from '@ant-design/plots/es/interface'; import React from 'react'; type ThreadProps = { data: JVMMetric[]; - chartConfig: G2plotConfig; + chartConfig: Chart; }; type Thread = { time: Date; @@ -44,8 +44,9 @@ const Thread: React.FC = (props) => { const config: AreaConfig = { ...chartConfig, data: dataListAll, - seriesField: 'name', - isStack: false, + colorField: 'name', + shapeField: 'name', + stack: false, tooltip: {} }; diff --git a/dinky-web/src/pages/Metrics/Server/index.tsx b/dinky-web/src/pages/Metrics/Server/index.tsx index a16ae227df..7e53196d63 100644 --- a/dinky-web/src/pages/Metrics/Server/index.tsx +++ b/dinky-web/src/pages/Metrics/Server/index.tsx @@ -22,15 +22,16 @@ import useHookRequest from '@/hooks/useHookRequest'; import { SseData } from '@/models/Sse'; import { SSE_TOPIC } from '@/pages/DevOps/constants'; import { MetricsTimeFilter } from '@/pages/DevOps/JobDetail/data'; -import { getMetricsData } from '@/pages/DevOps/JobDetail/srvice'; +import { getMetricsData } from '@/pages/DevOps/JobDetail/service'; import CPU from '@/pages/Metrics/Server/CPU'; import { JvmDataRecord, JVMMetric, MetricsDataType } from '@/pages/Metrics/Server/data'; import Heap from '@/pages/Metrics/Server/Heap'; import NonHeap from '@/pages/Metrics/Server/OutHeap'; import Thread from '@/pages/Metrics/Server/Thread'; +import { getChartThemeColor } from '@/utils/function'; import { useModel } from '@@/exports'; +import { LineOptions } from '@ant-design/plots/lib/core'; import { ProCard } from '@ant-design/pro-components'; -import { AreaOptions as G2plotConfig } from '@antv/g2plot/lib/plots/area/types'; import { Space } from 'antd'; import React, { useEffect, useState } from 'react'; @@ -46,6 +47,7 @@ type ServerProp = { const Server: React.FC = (props) => { const { timeRange } = props; + const [themeColor, setThemeColor] = useState(getChartThemeColor()); const [jvmData, setJvmData] = useState([]); @@ -77,17 +79,21 @@ const Server: React.FC = (props) => { } }, [timeRange]); - const commonConfig: G2plotConfig = { + useEffect(() => { + setThemeColor(getChartThemeColor()); + }, [getChartThemeColor()]); + + const commonConfig: LineOptions = { data: [], - autoFit: false, - animation: false, - height: 150, + autoFit: true, + theme: themeColor, + animation: { + update: { + type: false + } + }, yField: 'value', - xField: 'time', - xAxis: { - type: 'time', - mask: 'HH:mm:ss' - } + xField: (d) => new Date(d.time) }; const jvmMetric = jvmData[jvmData.length - 1]; const showLastData: JvmDataRecord = jvmMetric @@ -129,6 +135,8 @@ const Server: React.FC = (props) => { } extra={extraDataBuilder(showLastData).cpuLastValue} + bodyStyle={{ paddingBlock: 0, height: 200 }} + colSpan={'25%'} > @@ -140,6 +148,8 @@ const Server: React.FC = (props) => { } extra={extraDataBuilder(showLastData).heapLastValue} + bodyStyle={{ paddingBlock: 0, height: 200 }} + colSpan={'25%'} > @@ -151,6 +161,8 @@ const Server: React.FC = (props) => { } extra={extraDataBuilder(showLastData).threadCount} + bodyStyle={{ paddingBlock: 0, height: 200 }} + colSpan={'25%'} > @@ -162,6 +174,8 @@ const Server: React.FC = (props) => { } extra={extraDataBuilder(showLastData).nonHeapLastValue} + bodyStyle={{ paddingBlock: 0, height: 200 }} + colSpan={'25%'} > diff --git a/dinky-web/src/pages/Metrics/index.tsx b/dinky-web/src/pages/Metrics/index.tsx index ba0d26b79d..d93323f2c9 100644 --- a/dinky-web/src/pages/Metrics/index.tsx +++ b/dinky-web/src/pages/Metrics/index.tsx @@ -18,63 +18,67 @@ */ import MetricsFilter from '@/components/Flink/MetricsFilter/MetricsFilter'; -import useHookRequest from '@/hooks/useHookRequest'; -import { MetricsTimeFilter } from '@/pages/DevOps/JobDetail/data'; +import {MetricsTimeFilter} from '@/pages/DevOps/JobDetail/data'; import JobMetricsList from '@/pages/Metrics/JobMetricsList'; import Server from '@/pages/Metrics/Server'; -import { getAllConfig } from '@/pages/Metrics/service'; -import { l } from '@/utils/intl'; -import { PageContainer, ProCard } from '@ant-design/pro-components'; -import { Alert } from 'antd'; -import { useState } from 'react'; +import {l} from '@/utils/intl'; +import {PageContainer, ProCard} from '@ant-design/pro-components'; +import {Divider, Result} from 'antd'; +import React, {memo, useEffect, useState} from 'react'; +import {CONFIG_MODEL_ASYNC, SysConfigStateType} from "@/pages/SettingCenter/GlobalSetting/model"; +import {connect} from "@umijs/max"; +import {SettingConfigKeyEnum} from "@/pages/SettingCenter/GlobalSetting/SettingOverView/constants"; +import MarqueeAlert from "@/components/MarqueeAlert"; -export default () => { +const Metrics: React.FC = (props) => { const [timeRange, setTimeRange] = useState({ startTime: new Date().getTime() - 2 * 60 * 1000, endTime: new Date().getTime(), isReal: true }); + const [loading, setLoading] = useState(false); + + const {dispatch, enableMetricMonitor} = props; + + useEffect(() => { + setLoading(true); + dispatch({ + type: CONFIG_MODEL_ASYNC.queryMetricConfig, + payload: SettingConfigKeyEnum.METRIC.toLowerCase() + }) + setLoading(false); + }, []); - const showServer = useHookRequest(getAllConfig, { - defaultParams: [], - onSuccess: (res: any) => { - for (const config of res.metrics) { - if (config.key === 'sys.metrics.settings.sys.enable') { - return config.value; - } - } - return false; - } - }); const onTimeSelectChange = (filter: MetricsTimeFilter) => { setTimeRange(filter); }; return ( -
- - ) - } - header={{ extra: [] }} - content={ - <> - {showServer.data && ( - - - - )} - {/**/} - + ]}} + content={ + <> + {enableMetricMonitor ? <> + + + + + + :<> + {l('metrics.dinky.not.open')}} /> - } - > -
+ } + + } + /> ); }; + +export default connect( + ({SysConfig}: { SysConfig: SysConfigStateType }) => ({ + enableMetricMonitor: SysConfig.enableMetricMonitor + }) +)(memo(Metrics)); diff --git a/dinky-web/src/pages/SettingCenter/GlobalSetting/model.ts b/dinky-web/src/pages/SettingCenter/GlobalSetting/model.ts index 0abdd574b6..a25306b133 100644 --- a/dinky-web/src/pages/SettingCenter/GlobalSetting/model.ts +++ b/dinky-web/src/pages/SettingCenter/GlobalSetting/model.ts @@ -17,17 +17,20 @@ * */ -import { queryDsConfig, queryResourceConfig } from '@/pages/SettingCenter/GlobalSetting/service'; -import { BaseConfigProperties } from '@/types/SettingCenter/data'; -import { createModelTypes } from '@/utils/modelUtils'; -import { Effect } from '@@/plugin-dva/types'; -import { Reducer } from 'umi'; +import {queryConfigByKeyword, queryDsConfig, queryResourceConfig} from '@/pages/SettingCenter/GlobalSetting/service'; +import {BaseConfigProperties} from '@/types/SettingCenter/data'; +import {createModelTypes} from '@/utils/modelUtils'; +import {Effect} from '@@/plugin-dva/types'; +import {Reducer} from 'umi'; const SYS_CONFIG = 'SysConfig'; export type SysConfigStateType = { dsConfig: BaseConfigProperties[]; + metricConfig: BaseConfigProperties[]; + resourceConfig: BaseConfigProperties[]; enabledDs: boolean; + enableMetricMonitor: boolean; enableResource: boolean; }; @@ -36,81 +39,129 @@ export type ConfigModelType = { state: SysConfigStateType; effects: { queryDsConfig: Effect; + queryMetricConfig: Effect; queryResourceConfig: Effect; }; reducers: { saveDsConfig: Reducer; + saveMetricConfig: Reducer; + saveResourceConfig: Reducer; updateEnabledDs: Reducer; + updateEnableMetricMonitor: Reducer; updateEnableResource: Reducer; }; }; const ConfigModel: ConfigModelType = { - namespace: SYS_CONFIG, - state: { - dsConfig: [], - enabledDs: false, - enableResource: false - }, + namespace: SYS_CONFIG, + state: { + dsConfig: [], + metricConfig: [], + resourceConfig: [], + enabledDs: false, + enableMetricMonitor: false, + enableResource: false + }, - effects: { - *queryDsConfig({ payload }, { call, put }) { - const response: BaseConfigProperties[] = yield call(queryDsConfig, payload); - yield put({ - type: 'saveDsConfig', - payload: response || [] - }); - if (response && response.length > 0) { - const enabledDs = response.some( - (item: BaseConfigProperties) => - item.key === 'sys.dolphinscheduler.settings.enable' && item.value === true - ); + effects: { + * queryDsConfig({payload}, {call, put}) { + const response: BaseConfigProperties[] = yield call(queryDsConfig, payload); yield put({ - type: 'updateEnabledDs', - payload: enabledDs + type: 'saveDsConfig', + payload: response || [] }); - } - }, - *queryResourceConfig({ payload }, { call, put }) { - const response: BaseConfigProperties[] = yield call(queryResourceConfig, payload); - yield put({ - type: 'saveDsConfig', - payload: response || [] - }); - if (response && response.length > 0) { - const enableResource = response.some( - (item: BaseConfigProperties) => - item.key === 'sys.resource.settings.base.enable' && item.value === true - ); + if (response && response.length > 0) { + const enabledDs = response.some( + (item: BaseConfigProperties) => + item.key === 'sys.dolphinscheduler.settings.enable' && item.value === true + ); + yield put({ + type: 'updateEnabledDs', + payload: enabledDs + }); + } + }, + + + * queryMetricConfig({payload}, {call, put}) { + console.log(payload) + const response: BaseConfigProperties[] = yield call(queryConfigByKeyword, payload); yield put({ - type: 'updateEnableResource', - payload: enableResource + type: 'saveMetricConfig', + payload: response || [] }); - } - } - }, + console.log(response) + if (response && response.length > 0) { + const enableResource = response.some( + (item: BaseConfigProperties) => + item.key === 'sys.metrics.settings.sys.enable' && item.value === true + ); + yield put({ + type: 'updateEnableMetricMonitor', + payload: enableResource + }); + } + }, - reducers: { - saveDsConfig(state, { payload }) { - return { - ...state, - dsConfig: payload - }; - }, - updateEnabledDs(state, { payload }) { - return { - ...state, - enabledDs: payload - }; + * queryResourceConfig({payload}, {call, put}) { + const response: BaseConfigProperties[] = yield call(queryResourceConfig, payload); + yield put({ + type: 'saveResourceConfig', + payload: response || [] + }); + if (response && response.length > 0) { + const enableResource = response.some( + (item: BaseConfigProperties) => + item.key === 'sys.resource.settings.base.enable' && item.value === true + ); + yield put({ + type: 'updateEnableResource', + payload: enableResource + }); + } + } }, - updateEnableResource(state, { payload }) { - return { - ...state, - enableResource: payload - }; + + reducers: { + saveDsConfig(state, {payload}) { + return { + ...state, + dsConfig: payload + }; + }, + saveMetricConfig(state, {payload}) { + return { + ...state, + metricConfig: payload + }; + }, + saveResourceConfig(state, {payload}) { + return { + ...state, + resourceConfig: payload + }; + }, + updateEnableMetricMonitor(state, {payload}) { + return { + ...state, + enableMetricMonitor: payload + } + }, + updateEnabledDs(state, {payload}) { + return { + ...state, + enabledDs: payload + }; + }, + updateEnableResource(state, {payload}) { + return { + ...state, + enableResource: payload + }; + } } } -}; +; export const [CONFIG_MODEL, CONFIG_MODEL_ASYNC] = createModelTypes(ConfigModel); diff --git a/dinky-web/src/pages/SettingCenter/GlobalSetting/service.ts b/dinky-web/src/pages/SettingCenter/GlobalSetting/service.ts index 9ed453791d..4284af42b8 100644 --- a/dinky-web/src/pages/SettingCenter/GlobalSetting/service.ts +++ b/dinky-web/src/pages/SettingCenter/GlobalSetting/service.ts @@ -20,6 +20,10 @@ import { queryDataByParams } from '@/services/BusinessCrud'; import { API_CONSTANTS } from '@/services/endpoints'; +export async function queryConfigByKeyword(keyword: string) { + return await queryDataByParams(API_CONSTANTS.SYSTEM_GET_ONE_TYPE_CONFIG, { type: keyword }); +} + export async function queryDsConfig(keyword: string) { return await queryDataByParams(API_CONSTANTS.SYSTEM_GET_ONE_TYPE_CONFIG, { type: keyword }); } diff --git a/dinky-web/src/types/Public/data.ts b/dinky-web/src/types/Public/data.ts index fc5da78b48..ff6df47d17 100644 --- a/dinky-web/src/types/Public/data.ts +++ b/dinky-web/src/types/Public/data.ts @@ -20,7 +20,9 @@ export const THEME = { NAV_THEME: 'navTheme', dark: 'realDark', - light: 'light' + light: 'light', + CHART_THEME_DARK: 'classicDark', + CHART_THEME_LIGHT: 'classic' }; /** diff --git a/dinky-web/src/utils/function.tsx b/dinky-web/src/utils/function.tsx index 5263057c75..5da8a8a79c 100644 --- a/dinky-web/src/utils/function.tsx +++ b/dinky-web/src/utils/function.tsx @@ -133,6 +133,18 @@ export function setLocalThemeToStorage(defaultTheme?: string) { localStorage.setItem(THEME.NAV_THEME, defaultTheme ?? getLocalTheme()); } +/** + * get chart theme color by localStorage's theme + */ +export function getChartThemeColor() { + const theme = getLocalTheme(); + if (theme && theme === THEME.dark) { + return THEME.CHART_THEME_DARK; + } else { + return THEME.CHART_THEME_LIGHT; + } +} + /** * register editor key binding | 注册编辑器快捷键 * diff --git a/pom.xml b/pom.xml index ba6fe1d5d6..9bf153b2ad 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,7 @@ UTF-8 2.5.0 0.10.2 - 1.0.2 + 1.0.3 1.37.0 2.12 2.12.10