### What problem does this PR solve? fix: Add Task Executor to system panel #2061 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.10.0
| @@ -10,44 +10,6 @@ import { | |||
| } from 'recharts'; | |||
| import { CategoricalChartProps } from 'recharts/types/chart/generateCategoricalChart'; | |||
| const data = [ | |||
| { | |||
| name: 'Page A', | |||
| uv: 4000, | |||
| pv: 2400, | |||
| }, | |||
| { | |||
| name: 'Page B', | |||
| uv: 3000, | |||
| pv: 1398, | |||
| }, | |||
| { | |||
| name: 'Page C', | |||
| uv: 2000, | |||
| pv: 9800, | |||
| }, | |||
| { | |||
| name: 'Page D', | |||
| uv: 2780, | |||
| pv: 3908, | |||
| }, | |||
| { | |||
| name: 'Page E', | |||
| uv: 1890, | |||
| pv: 4800, | |||
| }, | |||
| { | |||
| name: 'Page F', | |||
| uv: 2390, | |||
| pv: 3800, | |||
| }, | |||
| { | |||
| name: 'Page G', | |||
| uv: 3490, | |||
| pv: 4300, | |||
| }, | |||
| ]; | |||
| interface IProps extends CategoricalChartProps { | |||
| data?: Array<{ xAxis: string; yAxis: number }>; | |||
| showLegend?: boolean; | |||
| @@ -1,7 +1,7 @@ | |||
| import { LanguageTranslationMap } from '@/constants/common'; | |||
| import { ResponseGetType } from '@/interfaces/database/base'; | |||
| import { ITenantInfo } from '@/interfaces/database/knowledge'; | |||
| import { ISystemStatus, IUserInfo } from '@/interfaces/database/userSetting'; | |||
| import { ISystemStatus, IUserInfo } from '@/interfaces/database/user-setting'; | |||
| import userService from '@/services/user-service'; | |||
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | |||
| import { message } from 'antd'; | |||
| @@ -20,11 +20,17 @@ export interface IUserInfo { | |||
| update_time: number; | |||
| } | |||
| export type TaskExecutorElapsed = Record<string, number[]>; | |||
| export interface ISystemStatus { | |||
| es: Es; | |||
| minio: Minio; | |||
| mysql: Minio; | |||
| redis: Redis; | |||
| task_executor: { | |||
| status: string; | |||
| elapsed: TaskExecutorElapsed; | |||
| }; | |||
| } | |||
| interface Redis { | |||
| @@ -18,3 +18,16 @@ | |||
| color: red; | |||
| } | |||
| } | |||
| .taskBarTooltip { | |||
| font-size: 16px; | |||
| } | |||
| .taskBar { | |||
| width: '100%'; | |||
| height: 200px; | |||
| } | |||
| .taskBarTitle { | |||
| font-size: 16px; | |||
| } | |||
| @@ -1,6 +1,9 @@ | |||
| import SvgIcon from '@/components/svg-icon'; | |||
| import { useFetchSystemStatus } from '@/hooks/user-setting-hooks'; | |||
| import { ISystemStatus, Minio } from '@/interfaces/database/userSetting'; | |||
| import { | |||
| ISystemStatus, | |||
| TaskExecutorElapsed, | |||
| } from '@/interfaces/database/user-setting'; | |||
| import { Badge, Card, Flex, Spin, Typography } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import lowerCase from 'lodash/lowerCase'; | |||
| @@ -9,6 +12,7 @@ import { useEffect } from 'react'; | |||
| import { toFixed } from '@/utils/common-util'; | |||
| import styles from './index.less'; | |||
| import TaskBarChat from './task-bar-chat'; | |||
| const { Text } = Typography; | |||
| @@ -23,6 +27,7 @@ const TitleMap = { | |||
| minio: 'MinIO Object Storage', | |||
| redis: 'Redis', | |||
| mysql: 'Mysql', | |||
| task_executor: 'Task Executor', | |||
| }; | |||
| const SystemInfo = () => { | |||
| @@ -48,7 +53,11 @@ const SystemInfo = () => { | |||
| type="inner" | |||
| title={ | |||
| <Flex align="center" gap={10}> | |||
| <SvgIcon name={key} width={26}></SvgIcon> | |||
| {key === 'task_executor' ? ( | |||
| <img src="/logo.svg" alt="" width={26} /> | |||
| ) : ( | |||
| <SvgIcon name={key} width={26}></SvgIcon> | |||
| )} | |||
| <span className={styles.title}> | |||
| {TitleMap[key as keyof typeof TitleMap]} | |||
| </span> | |||
| @@ -60,28 +69,34 @@ const SystemInfo = () => { | |||
| } | |||
| key={key} | |||
| > | |||
| {Object.keys(info) | |||
| .filter((x) => x !== 'status') | |||
| .map((x) => { | |||
| return ( | |||
| <Flex | |||
| key={x} | |||
| align="center" | |||
| gap={16} | |||
| className={styles.text} | |||
| > | |||
| <b>{upperFirst(lowerCase(x))}:</b> | |||
| <Text | |||
| className={classNames({ | |||
| [styles.error]: x === 'error', | |||
| })} | |||
| {key === 'task_executor' ? ( | |||
| <TaskBarChat | |||
| data={info.elapsed as TaskExecutorElapsed} | |||
| ></TaskBarChat> | |||
| ) : ( | |||
| Object.keys(info) | |||
| .filter((x) => x !== 'status') | |||
| .map((x) => { | |||
| return ( | |||
| <Flex | |||
| key={x} | |||
| align="center" | |||
| gap={16} | |||
| className={styles.text} | |||
| > | |||
| {toFixed(info[x as keyof Minio]) as any} | |||
| {x === 'elapsed' && ' ms'} | |||
| </Text> | |||
| </Flex> | |||
| ); | |||
| })} | |||
| <b>{upperFirst(lowerCase(x))}:</b> | |||
| <Text | |||
| className={classNames({ | |||
| [styles.error]: x === 'error', | |||
| })} | |||
| > | |||
| {toFixed((info as Record<string, any>)[x]) as any} | |||
| {x === 'elapsed' && ' ms'} | |||
| </Text> | |||
| </Flex> | |||
| ); | |||
| }) | |||
| )} | |||
| </Card> | |||
| ); | |||
| })} | |||
| @@ -0,0 +1,91 @@ | |||
| import { TaskExecutorElapsed } from '@/interfaces/database/user-setting'; | |||
| import { Divider, Flex } from 'antd'; | |||
| import { max } from 'lodash'; | |||
| import { | |||
| Bar, | |||
| BarChart, | |||
| CartesianGrid, | |||
| ResponsiveContainer, | |||
| Tooltip, | |||
| } from 'recharts'; | |||
| import styles from './index.less'; | |||
| interface IProps { | |||
| data: TaskExecutorElapsed; | |||
| } | |||
| const getColor = (value: number) => { | |||
| if (value > 120) { | |||
| return 'red'; | |||
| } else if (value <= 120 && value > 50) { | |||
| return '#faad14'; | |||
| } | |||
| return '#52c41a'; | |||
| }; | |||
| const getMaxLength = (data: TaskExecutorElapsed) => { | |||
| const lengths = Object.keys(data).reduce<number[]>((pre, cur) => { | |||
| pre.push(data[cur].length); | |||
| return pre; | |||
| }, []); | |||
| return max(lengths) ?? 0; | |||
| }; | |||
| const fillEmptyElementByMaxLength = (list: any[], maxLength: number) => { | |||
| if (list.length === maxLength) { | |||
| return list; | |||
| } | |||
| return list.concat( | |||
| new Array(maxLength - list.length).fill({ | |||
| value: 0, | |||
| actualValue: 0, | |||
| fill: getColor(0), | |||
| }), | |||
| ); | |||
| }; | |||
| const CustomTooltip = ({ active, payload }: any) => { | |||
| if (active && payload && payload.length) { | |||
| return ( | |||
| <div className="custom-tooltip"> | |||
| <p | |||
| className={styles.taskBarTooltip} | |||
| >{`${payload[0].payload.actualValue}`}</p> | |||
| </div> | |||
| ); | |||
| } | |||
| return null; | |||
| }; | |||
| const TaskBarChat = ({ data }: IProps) => { | |||
| const maxLength = getMaxLength(data); | |||
| return ( | |||
| <Flex gap="middle" vertical> | |||
| {Object.keys(data).map((key) => { | |||
| const list = data[key].map((x) => ({ | |||
| value: x > 120 ? 120 : x, | |||
| actualValue: x, | |||
| fill: getColor(x), | |||
| })); | |||
| const nextList = fillEmptyElementByMaxLength(list, maxLength); | |||
| return ( | |||
| <Flex key={key} className={styles.taskBar} vertical> | |||
| <b className={styles.taskBarTitle}>ID: {key}</b> | |||
| <ResponsiveContainer> | |||
| <BarChart data={nextList} barSize={20}> | |||
| <CartesianGrid strokeDasharray="3 3" /> | |||
| <Tooltip content={<CustomTooltip></CustomTooltip>} /> | |||
| <Bar dataKey="value" /> | |||
| </BarChart> | |||
| </ResponsiveContainer> | |||
| <Divider></Divider> | |||
| </Flex> | |||
| ); | |||
| })} | |||
| </Flex> | |||
| ); | |||
| }; | |||
| export default TaskBarChat; | |||