chore: logs

This commit is contained in:
jialin
2026-01-30 18:48:55 +08:00
parent 6c42b87c57
commit 22e42e279c
9 changed files with 328 additions and 223 deletions
+71
View File
@@ -0,0 +1,71 @@
import { HandlerOptions } from '@/hooks/use-chunk-fetch';
import useDownloadStream from '@/hooks/use-download-stream';
import { useIntl } from '@umijs/max';
import { Progress, notification } from 'antd';
import dayjs from 'dayjs';
const renderMessage = (title: string) => {
return (
<div
style={{
width: 280,
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden'
}}
>
{title}
</div>
);
};
const createFileName = (name: string) => {
const timestamp = dayjs().format('YYYY-MM-DD_HH-mm-ss');
const fileName = `${name}_${timestamp}.txt`;
return fileName;
};
const useDownloadLogs = () => {
const { downloadStream } = useDownloadStream();
const intl = useIntl();
const [api, contextHolder] = notification.useNotification({
stack: { threshold: 1 }
});
const downloadNotification = (
data: HandlerOptions & {
filename: string;
duration?: number;
chunkRequestRef: any;
}
) => {
api.open({
duration: data.duration,
message: renderMessage(data.filename),
key: data.filename,
closeIcon: (
<span>{intl.formatMessage({ id: 'common.button.cancel' })}</span>
),
description: <Progress percent={data.percent} size="small"></Progress>,
onClose() {
data.chunkRequestRef?.current?.abort();
notification.destroy?.(data.filename);
}
});
};
const handleDownloadLog = async (params: { url: string; name: string }) => {
downloadStream({
url: params.url,
filename: createFileName(params.name),
downloadNotification
});
};
return {
onDownloadLog: handleDownloadLog,
contextHolder
};
};
export default useDownloadLogs;
+1 -1
View File
@@ -229,7 +229,7 @@ export default {
'common.options.none': 'None',
'common.options.auto': 'Auto',
'common.search.empty': 'No matching results found.',
'common.button.downloadLog': 'Download Log',
'common.button.downloadLog': 'Download Logs',
'common.button.faq': 'FAQ',
'common.button.moreInfo': 'More Info',
'common.text.warning': 'Warning',
@@ -1,193 +0,0 @@
import Wrapper from '@/components/label-selector/wrapper';
import { DeleteOutlined } from '@ant-design/icons';
import { useHover } from 'ahooks';
import { Button, Divider, Flex, Input, Popover } from 'antd';
import React, { useRef, useState } from 'react';
import styled from 'styled-components';
const ItemContainer = styled.div`
display: flex;
margin-bottom: 12px;
align-items: center;
justify-content: flex-start;
.seprator {
display: flex;
flex: none;
width: 12px;
align-items: center;
justify-content: center;
color: var(--ant-color-text-tertiary);
}
.btn {
width: 24px;
margin-left: 10px;
flex: none;
}
`;
interface MetadataListProps {
dataList: any[];
label: React.ReactNode;
description?: React.ReactNode;
disabled?: boolean;
btnText?: string;
onAdd?: () => void;
onDelete?: (index: number, item: any) => void;
children?: (item: any, index: number) => React.ReactNode;
}
const MetadataList: React.FC<MetadataListProps> = ({
dataList,
label,
description,
disabled,
btnText,
children,
onDelete,
onAdd
}) => {
return (
<Wrapper
label={label}
description={description}
onAdd={onAdd}
disabled={disabled}
btnText={btnText}
styles={{
wrapper: {
padding: 0,
border: 'none'
}
}}
>
{dataList.map((item, index) => (
<ItemContainer key={index}>{children?.(item, index)}</ItemContainer>
))}
</Wrapper>
);
};
const DeleteWrapper = styled.span`
cursor: pointer;
color: var(--ant-color-text-tertiary);
&:hover {
color: var(--ant-color-error-text-hover);
}
`;
const Box = styled.div`
width: 100%;
.ant-input-suffix {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
&:hover {
.ant-input-suffix {
opacity: 1;
transition: opacity 0.2s ease-in-out;
}
}
`;
const Container = styled.div`
display: flex;
align-items: center;
border: 1px solid var(--ant-color-border);
border-radius: 4px;
padding: 8.5px 11px;
height: 40px;
width: 240px;
cursor: pointer;
&:hover {
border-color: var(--ant-color-primary-hover);
}
&:active {
box-shadow: var(--ant-input-active-shadow);
}
.holder {
color: var(--ant-color-text-placeholder);
}
`;
const CompareConditions: React.FC = () => {
const [open, setOpen] = useState(false);
const [filters, setFilters] = useState<string[]>([]);
const containerRef = useRef<HTMLDivElement>(null);
const isHovering = useHover(containerRef);
const onAdd = () => {
setFilters((prev) => [...prev, '']);
};
const onDelete = (index: number, item: any) => {
setFilters((prev) => prev.filter((_, i) => i !== index));
};
const handleOnChange = (value: string, index: number) => {
const newFilters = [...filters];
newFilters[index] = value;
setFilters(newFilters);
};
const FiltersDrop = () => {
return (
<>
<MetadataList
dataList={filters}
onAdd={onAdd}
onDelete={onDelete}
btnText="Add Filter"
label=""
>
{(item, index) => (
<Box>
<Input
key={index}
value={item}
placeholder="enter a model name"
suffix={
filters.length > 1 ? (
<DeleteWrapper>
<DeleteOutlined onClick={() => onDelete?.(index, item)} />
</DeleteWrapper>
) : null
}
onChange={(e) => handleOnChange(e.target.value, index)}
/>
</Box>
)}
</MetadataList>
<Divider style={{ margin: '12px 0' }} />
<Flex justify="end" gap={8}>
<Button size="middle" type="text">
Clear
</Button>
<Button type="primary" size="middle">
Confirm
</Button>
</Flex>
</>
);
};
return (
<Popover
trigger={'click'}
arrow={false}
placement="bottom"
content={FiltersDrop()}
styles={{
root: {
width: '240px'
}
}}
>
<Container>
<span className="holder">Export filter</span>
</Container>
</Popover>
);
};
export default CompareConditions;
@@ -8,7 +8,6 @@ import { BenchmarkListItem } from '../config/types';
import useQueryDetail from '../services/use-query-detail';
import Configure from './configure';
import Environment from './environment';
import Logs from './logs';
import Summary from './summary';
const Details: React.FC<{ currentData?: BenchmarkListItem }> = ({
@@ -43,13 +42,13 @@ const Details: React.FC<{ currentData?: BenchmarkListItem }> = ({
label: intl.formatMessage({ id: 'benchmark.detail.environment.title' }),
children: <Environment />,
icon: <IconFont type="icon-server02" />
},
{
key: 'logs',
label: intl.formatMessage({ id: 'benchmark.detail.logs.title' }),
children: <Logs />,
icon: <IconFont type="icon-logs" />
}
// {
// key: 'logs',
// label: intl.formatMessage({ id: 'benchmark.detail.logs.title' }),
// children: <Logs />,
// icon: <IconFont type="icon-logs" />
// }
];
const handleChangeTab = (key: string) => {
@@ -0,0 +1,88 @@
import DropdownButtons from '@/components/drop-down-buttons';
import IconFont from '@/components/icon-font';
import icons from '@/components/icon-font/icons';
import useDownloadLogs from '@/hooks/use-download-logs';
import { DownloadOutlined } from '@ant-design/icons';
import { BENCHMARKS_API } from '../apis';
import { BenchmarkStatusValueMap } from '../config';
import { BenchmarkListItem as ListItem } from '../config/types';
const actionList = [
{
key: 'edit',
label: 'common.button.edit',
icon: icons.EditOutlined
},
{
label: 'common.button.viewlog',
key: 'viewlog',
status: [
BenchmarkStatusValueMap.Claimed,
BenchmarkStatusValueMap.Running,
BenchmarkStatusValueMap.Error,
BenchmarkStatusValueMap.Completed
],
icon: <IconFont type="icon-logs" />
},
{
label: 'common.button.downloadLog',
key: 'download',
status: [
BenchmarkStatusValueMap.Claimed,
BenchmarkStatusValueMap.Running,
BenchmarkStatusValueMap.Error,
BenchmarkStatusValueMap.Completed
],
icon: <DownloadOutlined />
},
{
key: 'delete',
label: 'common.button.delete',
icon: icons.DeleteOutlined,
props: {
danger: true
}
}
];
interface RowActionsProps {
record: ListItem;
handleSelect: (key: string, record: ListItem) => void;
}
const RowActions: React.FC<RowActionsProps> = (props) => {
const { record, handleSelect } = props;
const { onDownloadLog, contextHolder } = useDownloadLogs();
const actions = actionList.filter((action) => {
if (action.key === 'viewlog' || action.key === 'download') {
return action.status?.includes(record.state);
}
return true;
});
const handleDownloadLog = async () => {
onDownloadLog({
url: `${BENCHMARKS_API}/${record.id}/logs`,
name: record.name
});
};
const onSelect = (val: string) => {
if (val === 'download') {
handleDownloadLog();
return;
}
handleSelect(val, record);
};
return (
<>
{contextHolder}
<DropdownButtons items={actions} onSelect={onSelect}></DropdownButtons>
</>
);
};
export default RowActions;
@@ -0,0 +1,104 @@
import LogsViewer from '@/components/logs-viewer/virtual-log-list';
import { useIntl } from '@umijs/max';
import { Modal } from 'antd';
import React, { useCallback, useEffect } from 'react';
type ViewModalProps = {
open: boolean;
url: string;
tail?: number;
status?: string;
onCancel: () => void;
};
const ViewLogsModal: React.FC<ViewModalProps> = (props) => {
const intl = useIntl();
const { open, onCancel, tail, url, status } = props || {};
const logsViewerRef = React.useRef<any>(null);
const requestRef = React.useRef<any>(null);
const contentRef = React.useRef<any>(null);
const handleCancel = useCallback(() => {
logsViewerRef.current?.abort();
onCancel();
}, [onCancel]);
useEffect(() => {
const handleKeyDown = (e: any) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
e.preventDefault();
if (contentRef.current) {
const range = document.createRange();
range.selectNodeContents(contentRef.current);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
}
}
};
if (open) {
document.addEventListener('keydown', handleKeyDown);
}
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [open]);
useEffect(() => {
if (!url) return;
if (!open) {
logsViewerRef.current?.abort();
requestRef.current?.current?.cancel?.();
}
return () => {
logsViewerRef.current?.abort();
requestRef.current?.current?.cancel?.();
};
}, [url, open]);
return (
<Modal
title={
<span className="flex flex-center">
<span style={{ fontWeight: 'var(--font-weight-bold)' }}>
{intl.formatMessage({ id: 'common.button.viewlog' })}
</span>
</span>
}
zIndex={3000}
open={open}
centered={true}
onCancel={handleCancel}
destroyOnHidden={true}
closeIcon={true}
maskClosable={false}
keyboard={true}
styles={{
wrapper: {
borderRadius: 0
}
}}
width="100%"
footer={null}
>
<div ref={contentRef}>
<LogsViewer
ref={logsViewerRef}
diffHeight={78}
url={url}
tail={undefined}
enableScorllLoad={true}
isDownloading={false}
params={{
follow: true
}}
></LogsViewer>
</div>
</Modal>
);
};
export default ViewLogsModal;
@@ -1,7 +1,5 @@
// columns.ts
import AutoTooltip from '@/components/auto-tooltip';
import DropdownButtons from '@/components/drop-down-buttons';
import icons from '@/components/icon-font/icons';
import StatusTag from '@/components/status-tag';
import { tableSorter } from '@/config/settings';
import { useIntl } from '@umijs/max';
@@ -10,25 +8,10 @@ import { ColumnsType } from 'antd/es/table';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useMemo } from 'react';
import RowActions from '../components/row-actions';
import { BenchmarkStatus, BenchmarkStatusLabelMap } from '../config';
import { BenchmarkListItem as ListItem } from '../config/types';
const actionList = [
{
key: 'edit',
label: 'common.button.edit',
icon: icons.EditOutlined
},
{
key: 'delete',
label: 'common.button.delete',
icon: icons.DeleteOutlined,
props: {
danger: true
}
}
];
const useBenchmarkColumns = (
sortOrder: string[],
handleSelect: (val: string, record: ListItem) => void,
@@ -174,10 +157,7 @@ const useBenchmarkColumns = (
showTitle: false
},
render: (value: string, record: ListItem) => (
<DropdownButtons
items={actionList}
onSelect={(val) => handleSelect(val, record)}
></DropdownButtons>
<RowActions record={record} handleSelect={handleSelect}></RowActions>
)
}
];
@@ -0,0 +1,44 @@
import { useState } from 'react';
import { BENCHMARKS_API } from '../apis';
import { BenchmarkListItem as ListItem } from '../config/types';
const useViewLogs = () => {
const [openModalStatus, setOpenModalStatus] = useState<{
open: boolean;
url: string;
tail?: number;
status?: string;
}>({
open: false,
url: '',
tail: 1000,
status: undefined
});
const openModal = (row?: ListItem) => {
setOpenModalStatus({
open: true,
url: `${BENCHMARKS_API}/${row?.id}/logs`,
tail: 1000,
status: row?.state || undefined
});
};
const closeModal = () => {
setOpenModalStatus({
open: false,
url: '',
tail: 1000,
status: undefined
});
};
return {
openViewLogsModalStatus: openModalStatus,
setOpenViewLogsModalStatus: setOpenModalStatus,
openViewLogsModal: openModal,
closeViewLogsModal: closeModal
};
};
export default useViewLogs;
+12
View File
@@ -21,11 +21,13 @@ import {
import AddBenchmarkModal from './components/add-benchmark-modal';
import LeftActions from './components/left-actions';
import RightActions from './components/right-actions';
import ViewLogsModal from './components/view-logs-modal';
import { FormData, BenchmarkListItem as ListItem } from './config/types';
import useBenchmarkColumns from './hooks/use-benchmark-columns';
import useColumnSettings from './hooks/use-column-settings';
import useCreateBenchmark from './hooks/use-create-benchmark';
import useExportData from './hooks/use-export-data';
import useViewLogs from './hooks/use-view-logs';
import useQueryDataset from './services/use-query-dataset';
const Benchmark: React.FC = () => {
@@ -55,6 +57,8 @@ const Benchmark: React.FC = () => {
const { dataList: modelList, fetchData: fetchModelList } = useQueryModelList({
getValue: (item: any) => item.name
});
const { openViewLogsModal, closeViewLogsModal, openViewLogsModalStatus } =
useViewLogs();
const { SettingsButton, selectedColumns } = useColumnSettings();
const { datasetList, fetchDatasetData } = useQueryDataset();
@@ -104,6 +108,8 @@ const Benchmark: React.FC = () => {
handleEditUser(row);
} else if (val === 'delete') {
handleDelete({ ...row, name: row.name });
} else if (val === 'viewlog') {
openViewLogsModal(row);
}
});
@@ -218,6 +224,12 @@ const Benchmark: React.FC = () => {
onCancel={handleModalCancel}
onOk={handleModalOk}
></AddBenchmarkModal>
<ViewLogsModal
open={openViewLogsModalStatus.open}
url={openViewLogsModalStatus.url}
tail={openViewLogsModalStatus.tail}
onCancel={closeViewLogsModal}
></ViewLogsModal>
<DeleteModal ref={modalRef}></DeleteModal>
</>
);