mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-12 19:09:02 +08:00
### What problem does this PR solve? feat: Show task_executor heartbeat #3409 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
4b3eeaa6ef
commit
3824c1fec0
@ -22,16 +22,23 @@ export interface IUserInfo {
|
||||
|
||||
export type TaskExecutorElapsed = Record<string, number[]>;
|
||||
|
||||
export interface TaskExecutorHeartbeatItem {
|
||||
boot_at: string;
|
||||
current: null;
|
||||
done: number;
|
||||
failed: number;
|
||||
lag: number;
|
||||
name: string;
|
||||
now: string;
|
||||
pending: number;
|
||||
}
|
||||
|
||||
export interface ISystemStatus {
|
||||
es: Es;
|
||||
storage: Storage;
|
||||
database: Database;
|
||||
redis: Redis;
|
||||
task_executor: {
|
||||
error?: string;
|
||||
status: string;
|
||||
elapsed?: TaskExecutorElapsed;
|
||||
};
|
||||
task_executor_heartbeat: Record<string, TaskExecutorHeartbeatItem[]>;
|
||||
}
|
||||
|
||||
interface Redis {
|
||||
|
@ -2,7 +2,7 @@ import SvgIcon from '@/components/svg-icon';
|
||||
import { useFetchSystemStatus } from '@/hooks/user-setting-hooks';
|
||||
import {
|
||||
ISystemStatus,
|
||||
TaskExecutorElapsed,
|
||||
TaskExecutorHeartbeatItem,
|
||||
} from '@/interfaces/database/user-setting';
|
||||
import { Badge, Card, Flex, Spin, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
@ -11,6 +11,7 @@ import upperFirst from 'lodash/upperFirst';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { toFixed } from '@/utils/common-util';
|
||||
import { isObject } from 'lodash';
|
||||
import styles from './index.less';
|
||||
import TaskBarChat from './task-bar-chat';
|
||||
|
||||
@ -27,7 +28,7 @@ const TitleMap = {
|
||||
storage: 'Object Storage',
|
||||
redis: 'Redis',
|
||||
database: 'Database',
|
||||
task_executor: 'Task Executor',
|
||||
task_executor_heartbeats: 'Task Executor',
|
||||
};
|
||||
|
||||
const IconMap = {
|
||||
@ -60,10 +61,13 @@ const SystemInfo = () => {
|
||||
type="inner"
|
||||
title={
|
||||
<Flex align="center" gap={10}>
|
||||
{key === 'task_executor' ? (
|
||||
{key === 'task_executor_heartbeats' ? (
|
||||
<img src="/logo.svg" alt="" width={26} />
|
||||
) : (
|
||||
<SvgIcon name={IconMap[key as keyof typeof IconMap]} width={26}></SvgIcon>
|
||||
<SvgIcon
|
||||
name={IconMap[key as keyof typeof IconMap]}
|
||||
width={26}
|
||||
></SvgIcon>
|
||||
)}
|
||||
<span className={styles.title}>
|
||||
{TitleMap[key as keyof typeof TitleMap]}
|
||||
@ -76,13 +80,15 @@ const SystemInfo = () => {
|
||||
}
|
||||
key={key}
|
||||
>
|
||||
{key === 'task_executor' ? (
|
||||
info?.elapsed ? (
|
||||
{key === 'task_executor_heartbeats' ? (
|
||||
isObject(info) ? (
|
||||
<TaskBarChat
|
||||
data={info.elapsed as TaskExecutorElapsed}
|
||||
data={info as Record<string, TaskExecutorHeartbeatItem[]>}
|
||||
></TaskBarChat>
|
||||
) : (
|
||||
<Text className={styles.error}>{info.error}</Text>
|
||||
<Text className={styles.error}>
|
||||
{typeof info.error === 'string' ? info.error : ''}
|
||||
</Text>
|
||||
)
|
||||
) : (
|
||||
Object.keys(info)
|
||||
|
@ -1,57 +1,47 @@
|
||||
import { TaskExecutorElapsed } from '@/interfaces/database/user-setting';
|
||||
import { TaskExecutorHeartbeatItem } from '@/interfaces/database/user-setting';
|
||||
import { Divider, Flex } from 'antd';
|
||||
import { max } from 'lodash';
|
||||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
Rectangle,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
} from 'recharts';
|
||||
|
||||
import { formatDate, formatTime } from '@/utils/date';
|
||||
import dayjs from 'dayjs';
|
||||
import { get } from 'lodash';
|
||||
import styles from './index.less';
|
||||
|
||||
interface IProps {
|
||||
data: TaskExecutorElapsed;
|
||||
data: Record<string, TaskExecutorHeartbeatItem[]>;
|
||||
}
|
||||
|
||||
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) => {
|
||||
const CustomTooltip = ({ active, payload, ...restProps }: any) => {
|
||||
if (active && payload && payload.length) {
|
||||
const taskExecutorHeartbeatItem: TaskExecutorHeartbeatItem = get(
|
||||
payload,
|
||||
'0.payload',
|
||||
{},
|
||||
);
|
||||
return (
|
||||
<div className="custom-tooltip">
|
||||
<p
|
||||
className={styles.taskBarTooltip}
|
||||
>{`${payload[0].payload.actualValue}`}</p>
|
||||
<div className="bg-slate-50 p-2 rounded-md border border-indigo-100">
|
||||
<div className="font-semibold text-lg">
|
||||
{formatDate(restProps.label)}
|
||||
</div>
|
||||
{Object.entries(taskExecutorHeartbeatItem).map(([key, val], index) => {
|
||||
return (
|
||||
<div key={index} className="flex gap-1">
|
||||
<span className="font-semibold">{`${key}: `}</span>
|
||||
<span>
|
||||
{key === 'now' || key === 'boot_at' ? formatDate(val) : val}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -60,32 +50,56 @@ const CustomTooltip = ({ active, payload }: any) => {
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
return Object.entries(data).map(([key, val]) => {
|
||||
const data = val.map((x) => ({
|
||||
...x,
|
||||
now: dayjs(x.now).valueOf(),
|
||||
failed: 5,
|
||||
}));
|
||||
const firstItem = data[0];
|
||||
const lastItem = data[data.length - 1];
|
||||
|
||||
const domain = [firstItem.now, lastItem.now];
|
||||
|
||||
return (
|
||||
<Flex key={key} className={styles.taskBar} vertical>
|
||||
<div className="flex gap-8">
|
||||
<b className={styles.taskBarTitle}>ID: {key}</b>
|
||||
<b className={styles.taskBarTitle}>Lag: {lastItem.lag}</b>
|
||||
<b className={styles.taskBarTitle}>Pending: {lastItem.pending}</b>
|
||||
</div>
|
||||
<ResponsiveContainer>
|
||||
<BarChart data={data} barSize={20}>
|
||||
<XAxis
|
||||
dataKey="now"
|
||||
type="number"
|
||||
scale={'time'}
|
||||
domain={domain}
|
||||
tickFormatter={(x) => formatTime(x)}
|
||||
allowDataOverflow
|
||||
angle={60}
|
||||
padding={{ left: 20, right: 20 }}
|
||||
tickMargin={20}
|
||||
/>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<Tooltip content={<CustomTooltip></CustomTooltip>} />
|
||||
<Legend wrapperStyle={{ bottom: -22 }} />
|
||||
<Bar
|
||||
dataKey="done"
|
||||
fill="#8884d8"
|
||||
activeBar={<Rectangle fill="pink" stroke="blue" />}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="failed"
|
||||
fill="#82ca9d"
|
||||
activeBar={<Rectangle fill="gold" stroke="purple" />}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
<Divider></Divider>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default TaskBarChat;
|
||||
|
@ -1,5 +1,19 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export function formatDate(date: any) {
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
return dayjs(date).format('DD/MM/YYYY HH:mm:ss');
|
||||
}
|
||||
|
||||
export function formatTime(date: any) {
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
return dayjs(date).format('HH:mm:ss');
|
||||
}
|
||||
|
||||
export function today() {
|
||||
return formatDate(dayjs());
|
||||
}
|
||||
@ -11,10 +25,3 @@ export function lastDay() {
|
||||
export function lastWeek() {
|
||||
return formatDate(dayjs().subtract(1, 'weeks'));
|
||||
}
|
||||
|
||||
export function formatDate(date: any) {
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
return dayjs(date).format('DD/MM/YYYY HH:mm:ss');
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user