### 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
| } from 'recharts'; | } from 'recharts'; | ||||
| import { CategoricalChartProps } from 'recharts/types/chart/generateCategoricalChart'; | 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 { | interface IProps extends CategoricalChartProps { | ||||
| data?: Array<{ xAxis: string; yAxis: number }>; | data?: Array<{ xAxis: string; yAxis: number }>; | ||||
| showLegend?: boolean; | showLegend?: boolean; |
| import { LanguageTranslationMap } from '@/constants/common'; | import { LanguageTranslationMap } from '@/constants/common'; | ||||
| import { ResponseGetType } from '@/interfaces/database/base'; | import { ResponseGetType } from '@/interfaces/database/base'; | ||||
| import { ITenantInfo } from '@/interfaces/database/knowledge'; | 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 userService from '@/services/user-service'; | ||||
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | ||||
| import { message } from 'antd'; | import { message } from 'antd'; |
| update_time: number; | update_time: number; | ||||
| } | } | ||||
| export type TaskExecutorElapsed = Record<string, number[]>; | |||||
| export interface ISystemStatus { | export interface ISystemStatus { | ||||
| es: Es; | es: Es; | ||||
| minio: Minio; | minio: Minio; | ||||
| mysql: Minio; | mysql: Minio; | ||||
| redis: Redis; | redis: Redis; | ||||
| task_executor: { | |||||
| status: string; | |||||
| elapsed: TaskExecutorElapsed; | |||||
| }; | |||||
| } | } | ||||
| interface Redis { | interface Redis { |
| color: red; | color: red; | ||||
| } | } | ||||
| } | } | ||||
| .taskBarTooltip { | |||||
| font-size: 16px; | |||||
| } | |||||
| .taskBar { | |||||
| width: '100%'; | |||||
| height: 200px; | |||||
| } | |||||
| .taskBarTitle { | |||||
| font-size: 16px; | |||||
| } |
| import SvgIcon from '@/components/svg-icon'; | import SvgIcon from '@/components/svg-icon'; | ||||
| import { useFetchSystemStatus } from '@/hooks/user-setting-hooks'; | 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 { Badge, Card, Flex, Spin, Typography } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import lowerCase from 'lodash/lowerCase'; | import lowerCase from 'lodash/lowerCase'; | ||||
| import { toFixed } from '@/utils/common-util'; | import { toFixed } from '@/utils/common-util'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import TaskBarChat from './task-bar-chat'; | |||||
| const { Text } = Typography; | const { Text } = Typography; | ||||
| minio: 'MinIO Object Storage', | minio: 'MinIO Object Storage', | ||||
| redis: 'Redis', | redis: 'Redis', | ||||
| mysql: 'Mysql', | mysql: 'Mysql', | ||||
| task_executor: 'Task Executor', | |||||
| }; | }; | ||||
| const SystemInfo = () => { | const SystemInfo = () => { | ||||
| type="inner" | type="inner" | ||||
| title={ | title={ | ||||
| <Flex align="center" gap={10}> | <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}> | <span className={styles.title}> | ||||
| {TitleMap[key as keyof typeof TitleMap]} | {TitleMap[key as keyof typeof TitleMap]} | ||||
| </span> | </span> | ||||
| } | } | ||||
| key={key} | 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> | </Card> | ||||
| ); | ); | ||||
| })} | })} |
| 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; |