chore: replace components with core-ui, upgrade eslint

This commit is contained in:
jialin
2026-04-24 14:28:30 +08:00
committed by jialin
parent d48aa99dcc
commit 8711c27b78
470 changed files with 1017 additions and 24093 deletions
-33
View File
@@ -1,33 +0,0 @@
export const controlSeqRegex = /\x1b\[(\d*);?(\d*)?([A-DJKHfm])/g;
export const replaceLineRegex = /\r\n/g;
export const PageSize = 1000;
export const throttle = <T extends (...args: any[]) => void>(
func: T,
wait: number
): ((this: ThisParameterType<T>, ...args: Parameters<T>) => void) => {
let timeout: ReturnType<typeof setTimeout> | null = null;
let previous = Date.now();
return function (this: ThisParameterType<T>, ...args: Parameters<T>): void {
const now = Date.now();
const remaining = wait - (now - previous);
const context = this as ThisParameterType<T>;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(() => {
previous = Date.now();
timeout = null;
func.apply(context, args);
}, remaining);
}
};
};
-155
View File
@@ -1,155 +0,0 @@
import useOverlayScroller from '@/hooks/use-overlay-scroller';
import classNames from 'classnames';
import _, { throttle } from 'lodash';
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState
} from 'react';
import './styles/logs-list.less';
interface LogsListProps {
dataList: any[];
height?: number;
onScroll?: (data: { isTop: boolean; isBottom: boolean }) => void;
diffHeight?: number;
showNum?: boolean;
ref?: any;
}
const LogsList: React.FC<LogsListProps> = forwardRef((props, ref) => {
const { dataList, height, showNum, onScroll, diffHeight = 96 } = props;
const {
initialize,
updateScrollerPosition,
updateScrollerPositionToTop,
generateInstance,
scrollEventElement,
instance,
initialized
} = useOverlayScroller({
options: {
scrollbars: {
theme: 'os-theme-light'
}
}
});
const [innerHieght, setInnerHeight] = useState(
window.innerHeight - diffHeight
);
const scroller = useRef<any>({});
const stopScroll = useRef(false);
const scrollToBottom = useCallback(() => {
updateScrollerPosition(0);
}, [updateScrollerPosition]);
const debounceResetStopScroll = _.debounce(() => {
stopScroll.current = false;
}, 30000);
const scrollToTop = useCallback(() => {
stopScroll.current = true;
updateScrollerPositionToTop();
debounceResetStopScroll();
}, [updateScrollerPositionToTop]);
useImperativeHandle(ref, () => ({
scrollToBottom,
scrollToTop,
scroller: scroller.current
}));
const handleOnWheel = (e: any) => {
const scrollTop = scrollEventElement?.current?.scrollTop;
const scrollHeight = scrollEventElement?.current?.scrollHeight;
const clientHeight = scrollEventElement?.current?.clientHeight;
stopScroll.current = scrollTop + clientHeight <= scrollHeight;
const isBottom = scrollTop + clientHeight + 150 >= scrollHeight;
// is scroll to top
if (scrollTop <= 10) {
onScroll?.({
isTop: true,
isBottom: false
});
} else if (isBottom) {
onScroll?.({
isTop: false,
isBottom: true
});
stopScroll.current = false;
} else {
onScroll?.({
isTop: false,
isBottom: false
});
}
};
const debounceUpdateScrollerPosition = _.debounce(() => {
generateInstance();
updateScrollerPosition(0);
}, 200);
useEffect(() => {
const handleResize = throttle(() => {
const viewportHeight = window.innerHeight;
const viewHeight = viewportHeight - diffHeight;
setInnerHeight(viewHeight);
}, 100);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, [diffHeight]);
useEffect(() => {
if (scroller.current) {
initialize(scroller.current);
}
}, [initialize]);
useEffect(() => {
if (dataList.length && !stopScroll.current && instance) {
updateScrollerPosition(0);
} else if (dataList.length && !stopScroll.current && scroller.current) {
if (!initialized) {
initialize(scroller.current);
}
if (!instance) {
debounceUpdateScrollerPosition();
} else {
updateScrollerPosition(0);
}
}
}, [dataList, stopScroll.current, instance, scroller.current]);
return (
<div
className="logs-wrap"
style={{ height: innerHieght }}
ref={scroller}
onWheel={handleOnWheel}
>
<div className={classNames('content')}>
{_.map(dataList, (item: any, index: number) => {
return (
<div
key={item.uid}
className={classNames('text')}
data-uid={item.uid}
>
{item.content}
</div>
);
})}
</div>
</div>
);
});
export default React.memo(LogsList);
@@ -1,109 +0,0 @@
import {
DownOutlined,
UpOutlined,
VerticalLeftOutlined
} from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Tooltip } from 'antd';
import React from 'react';
import './styles/pagination.less';
interface LogsPaginationProps {
page: number;
total: number;
pageSize?: number;
onPrev?: () => void;
onNext?: () => void;
onBackend?: () => void;
onToFirst?: () => void;
}
const LogsPagination: React.FC<LogsPaginationProps> = (props) => {
const { page, total, pageSize, onNext, onPrev, onBackend, onToFirst } = props;
const intl = useIntl();
const handleOnPrev = () => {
onPrev?.();
};
const handleOnNext = () => {
onNext?.();
};
return (
<div className="pagination">
{
<>
<Tooltip
title={intl.formatMessage({ id: 'models.logs.pagination.first' })}
placement="left"
>
<Button
onClick={onToFirst}
type="text"
shape="circle"
style={{ color: 'rgba(255,255,255,.7)', marginBottom: 10 }}
>
<VerticalLeftOutlined rotate={-90} />
</Button>
</Tooltip>
<Tooltip
placement="left"
title={intl.formatMessage(
{ id: 'models.logs.pagination.prev' },
{ lines: pageSize }
)}
>
<Button
onClick={handleOnPrev}
type="text"
shape="circle"
style={{ color: 'rgba(255,255,255,.7)' }}
>
<UpOutlined />
</Button>
</Tooltip>
</>
}
<span className="pages">
<span className="curr">{page}</span> /{' '}
<span className="total">{total}</span>
</span>
{page < total && (
<>
<Tooltip
placement="left"
title={intl.formatMessage(
{ id: 'models.logs.pagination.next' },
{ lines: pageSize }
)}
>
<Button
onClick={handleOnNext}
type="text"
shape="circle"
style={{ color: 'rgba(255,255,255,.7)' }}
>
<DownOutlined />
</Button>
</Tooltip>
<Tooltip
title={intl.formatMessage({ id: 'models.logs.pagination.last' })}
placement="left"
>
<Button
onClick={onBackend}
type="text"
shape="circle"
style={{ color: 'rgba(255,255,255,.7)', marginTop: 10 }}
>
<VerticalLeftOutlined rotate={90} />
</Button>
</Tooltip>
</>
)}
</div>
);
};
export default LogsPagination;
-315
View File
@@ -1,315 +0,0 @@
import { controlSeqRegex } from './config';
const removeBrackets = (str: string) => {
return str?.replace?.(/^\(…\)/, '');
};
const removeBracketsFromLine = (row: string) => {
return row.startsWith('(…)') ? row.slice(3) : row;
};
interface MessageProps {
inputStr: string;
reset?: boolean;
page?: number;
isComplete?: boolean;
chunked?: boolean;
progress?: number;
percent?: number;
isDownloading?: boolean;
}
class AnsiParser {
private cursorRow: number = 0;
private cursorCol: number = 0;
private screen: string[][] = [['']];
private rawDataRows: number = 0;
private uid: number = 0;
private isProcessing: boolean = false;
private taskQueue: string[] = [];
private page: number = 1;
private progress: number = 0;
private percent: number = 0;
private isComplete: boolean = false;
private chunked: boolean = true; // true: send data in chunks, false: send all data at once
private reminder: string = '';
private lines: string[] = [];
isDownloading: boolean = false;
private pageSize: number = 500;
private colorMap = {
'30': 'black',
'31': 'red',
'32': 'green',
'33': 'yellow',
'34': 'blue',
'35': 'magenta',
'36': 'cyan',
'37': 'white'
};
constructor() {
this.reset();
}
public reset() {
this.cursorRow = 0;
this.cursorCol = 0;
this.screen = [['']];
this.rawDataRows = 0;
this.uid = this.uid + 1;
this.lines = [];
this.reminder = '';
this.page = 1;
}
public setPage(page: number | undefined) {
this.page = page ?? 1;
}
public setPercent(percent: number | undefined) {
this.percent = percent ?? 0;
}
public setProgress(progress: number | undefined) {
this.progress = progress ?? 0;
}
public setIsCompelete(isComplete: boolean) {
this.isComplete = isComplete;
}
public setChunked(chunked: boolean) {
this.chunked = chunked ?? true;
}
public setIsDownloading(isDownloading: boolean) {
this.isDownloading = isDownloading;
}
private setId() {
this.uid += 1;
return this.uid;
}
private handleText(text: string) {
for (let i = 0; i < text.length; i++) {
let char = text[i];
if (char === '\r') {
let nextChar = text[i + 1];
if (nextChar === '\n') {
continue; // windows new line: \r\n
} else {
this.cursorCol = 0; // move to the beginning of the line
}
} else if (char === '\n') {
this.rawDataRows++;
this.cursorRow++;
this.cursorCol = 0; // back to the beginning of the line
if (!this.screen[this.cursorRow]) {
this.screen[this.cursorRow] = [''];
}
} else {
const currentLine = this.screen[this.cursorRow];
currentLine[this.cursorCol] = char;
this.cursorCol++;
}
}
}
private handleAnsiSequence(match: RegExpExecArray, isEnd: boolean) {
const n = parseInt(match[1] || '1', 10);
const m = parseInt(match[2] || '1', 10);
const command = match[3];
switch (command) {
case 'A':
this.cursorRow = Math.max(0, this.cursorRow - n);
break;
case 'B':
this.cursorRow += n;
break;
case 'C': // move the cursor to the right
this.cursorCol += n;
break;
case 'D': // move the cursor to the left
this.cursorCol = Math.max(0, this.cursorCol - n);
break;
case 'H': // move the cursor to the specified position (n, m)
this.cursorRow = Math.max(0, n - 1);
this.cursorCol = Math.max(0, m - 1);
break;
case 'J': // clear the screen
if (n === 2) {
this.reset();
}
break;
case 'm':
// if (match[1] === '0') {
// currentStyle = '';
// } else if (colorMap[match[1]]) {
// currentStyle = `color: ${colorMap[match[1]]};`;
// }
break;
}
while (this.screen.length <= this.cursorRow && !isEnd) {
this.screen.push(['']);
}
while (this.screen[this.cursorRow].length <= this.cursorCol && !isEnd) {
this.screen[this.cursorRow].push('');
}
}
private processInput(input: string) {
let match: RegExpExecArray | null;
let lastIndex = 0;
while ((match = controlSeqRegex.exec(input)) !== null) {
const textBeforeControl = input.slice(lastIndex, match.index);
this.handleText(textBeforeControl);
lastIndex = controlSeqRegex.lastIndex;
this.handleAnsiSequence(match, lastIndex === input.length - 1);
}
const remainingText = input.slice(lastIndex);
this.handleText(remainingText);
// const result = this.screen.map((row, index) => ({
// content: removeBracketsFromLine(row.join('')),
// uid: `${this.page}-${index}`
// }));
const result = this.screen.map((row, index) =>
removeBracketsFromLine(row.join(''))
);
return {
data: result,
lines: this.rawDataRows,
remainder: ''
};
}
private getScreenText() {
const result = this.screen
.map((row) => removeBracketsFromLine(row.join('')))
.join('\n');
return result;
}
private processInputByLine(input: string): {
data: string[];
lines: number;
remainder: string;
} {
const lines = input?.split(/\r?\n/) || [];
const remainder = lines.pop() || '';
// const data = lines.join('\n');
this.rawDataRows += lines.length;
lines.forEach((line) => {
this.lines.push(line);
});
return {
data: this.lines,
lines: this.rawDataRows,
remainder
};
}
private getAllLines() {
return this.lines.join('\n');
}
private async processQueue(): Promise<void> {
if (this.isProcessing) {
return;
}
this.isProcessing = true;
while (this.taskQueue.length > 0) {
let input = '';
if (this.isDownloading) {
input = this.taskQueue.shift() || '';
} else {
input = this.reminder + this.taskQueue.shift();
}
if (input) {
try {
const result = this.isDownloading
? this.processInput(input)
: this.processInputByLine(input);
if (!this.isDownloading) {
this.reminder = result.remainder;
}
if (this.chunked) {
self.postMessage({ result: result.data, lines: result.lines });
} else if (!this.isComplete) {
self.postMessage({
result: '',
percent: this.percent,
isComplete: false
});
}
} catch (error) {
console.error('Error processing input:', error);
}
}
await new Promise((resolve) => {
setTimeout(resolve, 0);
});
}
this.isProcessing = false;
if (this.taskQueue.length > 0) {
this.processQueue();
} else if (this.isComplete && !this.chunked) {
self.postMessage({
result: this.getAllLines(),
percent: this.percent,
isComplete: true
});
this.reset();
}
}
public enqueueData(input: string): void {
this.taskQueue.push(input);
if (!this.isProcessing) {
this.processQueue();
}
}
}
const parser = new AnsiParser();
self.onmessage = function (event: MessageEvent<MessageProps>) {
const {
inputStr,
reset,
page,
isComplete = false,
chunked = true,
percent = 0,
isDownloading = false
} = event.data;
parser.setIsDownloading(isDownloading);
parser.setPage(page);
parser.setIsCompelete(isComplete);
parser.setChunked(chunked);
parser.setPercent(percent);
if (reset) {
parser.reset();
}
parser.enqueueData(inputStr);
};
self.onerror = function (event) {
console.error('parse logs error===', event);
};
@@ -1,126 +0,0 @@
.logs-viewer-wrap-w2 {
position: relative;
.pg {
position: absolute;
top: 16px;
right: 20px;
width: 40px;
height: 130px;
.pg-inner {
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
right: -18px;
// width: 80px;
height: 185px;
&:hover {
.pagination {
display: flex;
}
}
}
.pagination {
display: none;
}
&:hover {
.pagination {
display: flex;
}
}
&.at-top {
.pagination {
display: flex;
}
}
}
}
.loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
padding-top: 100px;
display: flex;
justify-content: center;
background-color: var(--color-fill-spin-bg);
}
.copy {
position: absolute;
top: 10px;
right: 10px;
z-index: 100;
button {
color: rgba(255, 255, 255, 70%);
background-color: rgba(71, 71, 71, 100%);
&:hover {
color: rgba(255, 255, 255, 90%) !important;
background-color: rgba(71, 71, 71, 100%) !important;
}
}
}
.wrap {
padding: 5px 0 2px 10px;
background-color: var(--color-logs-bg);
border-radius: var(--border-radius-mini);
font-family: monospace, Menlo, Courier, 'Courier New', Consolas, Monaco,
'Liberation Mono' !important;
.content {
word-wrap: break-word;
height: 100%;
padding-right: 2px;
&.line-break {
word-wrap: break-word;
}
.text {
min-height: 22px;
}
color: var(--color-logs-text);
font-size: var(--font-size-small);
line-height: 22px;
white-space: pre-wrap;
background-color: var(--color-logs-bg);
}
}
.xterm {
.xterm-viewport {
overflow-y: auto !important;
&::-webkit-scrollbar {
width: var(--scrollbar-size);
height: var(--scrollbar-size);
}
&::-webkit-scrollbar-thumb {
background-color: var(--color-scrollbar-thumb);
border-radius: 4px;
}
&::-webkit-scrollbar-track {
background-color: var(--color-scrollbar-track);
border-radius: 4px;
}
}
}
}
@@ -1,44 +0,0 @@
.logs-wrap {
background-color: var(--color-logs-bg);
border-radius: var(--border-radius-mini);
font-family:
monospace, Menlo, Courier, 'Courier New', Consolas, Monaco,
'Liberation Mono' !important;
.content {
word-wrap: break-word;
height: 100%;
padding-right: 2px;
&.line-break {
word-wrap: break-word;
}
.text {
min-height: 22px;
padding-inline-end: 80px;
&.numable {
position: relative;
padding-left: 45px;
.line-num {
display: flex;
align-items: center;
width: 40px;
justify-content: center;
position: absolute;
left: 0;
top: 0;
background-color: rgba(71, 71, 71, 50%);
}
}
}
color: var(--color-logs-text);
font-size: 12px;
line-height: 22px;
white-space: pre-wrap;
background-color: var(--color-logs-bg);
}
}
@@ -1,25 +0,0 @@
.pagination {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: rgba(255, 255, 255, 100%);
gap: 5px;
.ant-btn:hover {
color: rgba(255, 255, 255, 90%) !important;
background-color: rgba(71, 71, 71, 100%) !important;
}
.ant-btn {
background-color: rgba(71, 71, 71, 70%) !important;
}
.pages {
display: flex;
justify-content: center;
align-items: center;
height: 38px;
width: 38px;
}
}
@@ -1,50 +0,0 @@
.logs-viewer-wrap-w2 {
.wrap {
padding: 5px 0 5px 10px;
background-color: var(--color-logs-bg);
border-radius: var(--border-radius-mini);
overflow: hidden;
.content {
word-wrap: break-word;
height: 100%;
&.line-break {
word-wrap: break-word;
}
.text {
height: 100%;
}
color: var(--color-logs-text);
font-size: var(--font-size-small);
line-height: 22px;
white-space: pre-wrap;
background-color: var(--color-logs-bg);
}
}
.xterm {
// height: 100% !important;
.xterm-viewport {
overflow-y: auto !important;
&::-webkit-scrollbar {
width: var(--scrollbar-size);
height: var(--scrollbar-size);
}
&::-webkit-scrollbar-thumb {
background-color: var(--color-scrollbar-thumb);
border-radius: 4px;
}
&::-webkit-scrollbar-track {
background-color: var(--color-scrollbar-track);
border-radius: 4px;
}
}
}
}
@@ -1,41 +0,0 @@
import { useState } from 'react';
import { PageSize } from './config';
const useLogsPagination = () => {
const [pageSize, setPageSize] = useState(PageSize);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(1);
const nextPage = () => {
setPage(page + 1);
};
const prePage = () => {
let newPage = page - 1;
if (newPage < 1) {
newPage = 1;
}
setPage(newPage);
};
const resetPage = () => {
setPage(1);
};
const setTotalPage = (total: number) => {
setTotal(total);
};
return {
nextPage,
resetPage,
prePage,
setPage,
pageSize,
setTotalPage,
page,
totalPage: total
};
};
export default useLogsPagination;
-34
View File
@@ -1,34 +0,0 @@
import { useEffect, useState } from 'react';
export const useResizeObserver = (ref: React.RefObject<HTMLElement>) => {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
const element = ref.current;
if (!element) return;
const updateSize = () => {
const rect = element.getBoundingClientRect();
setSize((prev) => {
if (prev.width === rect.width && prev.height === rect.height)
return prev;
return { width: rect.width, height: rect.height };
});
};
const observer = new ResizeObserver(() => {
updateSize();
});
observer.observe(element);
updateSize();
return () => {
observer.disconnect();
};
}, [ref.current]);
return size;
};
export default useResizeObserver;
@@ -1,345 +0,0 @@
import useSetChunkFetch from '@/hooks/use-chunk-fetch';
import { useMemoizedFn } from 'ahooks';
import { Spin } from 'antd';
import classNames from 'classnames';
import _ from 'lodash';
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState
} from 'react';
import LogsList from './logs-list';
import LogsPagination from './logs-pagination';
import './styles/index.less';
import useLogsPagination from './use-logs-pagination';
interface LogsViewerProps {
height?: number;
content?: string;
url: string;
params?: object;
ref?: any;
tail?: number;
enableScorllLoad?: boolean;
diffHeight?: number;
isDownloading?: boolean;
}
const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
const {
diffHeight,
url,
tail: defaultTail,
enableScorllLoad = true,
isDownloading
} = props;
const { pageSize, page, setPage, setTotalPage, totalPage } =
useLogsPagination();
const { setChunkFetch } = useSetChunkFetch();
const chunkRequedtRef = useRef<any>(null);
const [logs, setLogs] = useState<any[]>([]);
const logParseWorker = useRef<any>(null);
const tail = useRef<any>(defaultTail);
const [loading, setLoading] = useState(false);
const [isAtTop, setIsAtTop] = useState(false);
const [scrollPos, setScrollPos] = useState<any[]>([]);
const logListRef = useRef<any>(null);
const loadMoreDone = useRef(false);
const pageRef = useRef<any>(page);
const totalPageRef = useRef<any>(totalPage);
const isLoadingMoreRef = useRef(false);
const [currentData, setCurrentPageData] = useState<any[]>([]);
const scrollPosRef = useRef<any>({
pos: 'bottom',
page: 1
});
const lineCountRef = useRef(0);
const clearScreen = useRef(false);
useImperativeHandle(ref, () => ({
abort() {
chunkRequedtRef.current?.current?.abort?.();
logParseWorker.current?.terminate?.();
}
}));
const removeBracketsFromLine = (row: string) => {
return row.startsWith('(…)') ? row.slice(3) : row;
};
const setCurrentData = (lines: string[]) => {
const dataList = lines.map((line, index) => {
return {
content: line,
uid: `${pageRef.current}-${index}`
};
});
setCurrentPageData(dataList);
};
const debounceLoading = _.debounce(() => {
setLoading(false);
isLoadingMoreRef.current = false;
if (logListRef.current?.scroller) {
logListRef.current.scroller.style['pointer-events'] = 'auto';
}
}, 1000);
const getCurrent = useCallback(() => {
if (pageRef.current < 1) {
pageRef.current = 1;
}
const start = (pageRef.current - 1) * pageSize;
const end = pageRef.current * pageSize;
const currentLogs = logs.slice(start, end);
setPage(pageRef.current);
setCurrentData(currentLogs);
}, [logs, pageSize]);
const getPrePage = useCallback(() => {
pageRef.current = pageRef.current - 1;
getCurrent();
setScrollPos(['bottom', pageRef.current]);
scrollPosRef.current = {
pos: 'bottom',
page: pageRef.current
};
}, [getCurrent]);
const getNextPage = useCallback(() => {
pageRef.current = pageRef.current + 1;
getCurrent();
setScrollPos(['top', pageRef.current]);
scrollPosRef.current = {
pos: 'top',
page: pageRef.current
};
}, [getCurrent]);
const handleonBackend = useCallback(() => {
pageRef.current = totalPageRef.current;
getCurrent();
console.log('pageRef.current', pageRef.current);
setScrollPos(['bottom', pageRef.current]);
scrollPosRef.current = {
pos: 'bottom',
page: pageRef.current
};
}, [getCurrent]);
const handleonToFirst = useCallback(() => {
pageRef.current = 1;
getCurrent();
setScrollPos(['top', pageRef.current]);
scrollPosRef.current = {
pos: 'top',
page: pageRef.current
};
}, [getCurrent]);
const updateContent = (data: string) => {
if (isLoadingMoreRef.current) {
setLoading(true);
if (logListRef.current?.scroller) {
logListRef.current.scroller.style['pointer-events'] = 'none';
}
}
logParseWorker.current.postMessage({
inputStr: data,
page: pageRef.current,
reset: clearScreen.current,
isDownloading: isDownloading
});
clearScreen.current = false;
};
const createChunkConnection = async () => {
chunkRequedtRef.current?.current?.abort?.();
chunkRequedtRef.current = setChunkFetch({
url,
params: {
...props.params,
tail: tail.current,
watch: true
},
contentType: 'text',
handler: updateContent
});
};
const handleOnScroll = useMemoizedFn(
async (data: { isTop: boolean; isBottom: boolean }) => {
const { isTop, isBottom } = data;
setIsAtTop(isTop);
if (isBottom) {
scrollPosRef.current = {
pos: 'bottom',
page: page
};
} else if (isTop) {
scrollPosRef.current = {
pos: 'top',
page: page
};
} else {
scrollPosRef.current = {
pos: 'middle',
page: page
};
}
if (
loading ||
(logs.length > 0 &&
lineCountRef.current < pageSize - 1 &&
!loadMoreDone.current) ||
!enableScorllLoad
) {
return;
}
if (isTop && !loadMoreDone.current) {
tail.current = undefined;
createChunkConnection();
loadMoreDone.current = true;
isLoadingMoreRef.current = true;
clearScreen.current = true;
} else if (isTop && page <= totalPage && page > 1) {
// getPrePage();
} else if (isBottom && page < totalPage) {
// getNextPage();
}
}
);
const debouncedScroll = useCallback(
_.throttle(() => {
if (scrollPos[0] === 'top' && scrollPosRef.current.pos === 'top') {
logListRef.current?.scrollToTop();
}
if (scrollPosRef.current.pos === 'bottom') {
logListRef.current?.scrollToBottom();
}
}, 150),
[scrollPos]
);
useEffect(() => {
createChunkConnection();
return () => {
chunkRequedtRef.current?.current?.abort?.();
};
}, [url, isDownloading]);
useEffect(() => {
debouncedScroll();
}, [scrollPos]);
useEffect(() => {
logParseWorker.current?.terminate?.();
logParseWorker.current = new Worker(
// @ts-ignore
new URL('./parse-worker.ts', import.meta.url),
{
type: 'module'
}
);
logParseWorker.current.onmessage = (event: any) => {
const { result, lines } = event.data;
lineCountRef.current = lines;
if (pageRef.current < 1) {
pageRef.current = 1;
}
const oldTotalPage = totalPageRef.current;
totalPageRef.current = Math.ceil(result.length / pageSize);
if (isLoadingMoreRef.current) {
pageRef.current = totalPageRef.current;
} else if (
pageRef.current === oldTotalPage &&
scrollPosRef.current.pos === 'bottom'
) {
scrollPosRef.current = {
pos: 'bottom',
page: pageRef.current
};
pageRef.current = totalPageRef.current;
setScrollPos(['bottom', pageRef.current]);
}
const start = (pageRef.current - 1) * pageSize;
const end = pageRef.current * pageSize;
const currentLogs = result.slice(start, end);
setLogs(result);
setTotalPage(totalPageRef.current);
setPage(pageRef.current);
setCurrentData(currentLogs);
debounceLoading();
};
return () => {
if (logParseWorker.current) {
logParseWorker.current.terminate();
}
};
}, []);
return (
<div className="logs-viewer-wrap-w2">
<div className="wrap">
<div>
<LogsList
ref={logListRef}
dataList={currentData}
diffHeight={diffHeight}
onScroll={handleOnScroll}
></LogsList>
</div>
{loading && (
<Spin
size="middle"
spinning={loading}
className={classNames({
loading: loading
})}
></Spin>
)}
{totalPage > 1 && (
<div className="pg">
<div
className={classNames('pg-inner', {
'at-top': true
})}
>
<LogsPagination
page={page}
total={totalPage}
pageSize={pageSize}
onNext={getNextPage}
onPrev={getPrePage}
onToFirst={handleonToFirst}
onBackend={handleonBackend}
></LogsPagination>
</div>
</div>
)}
</div>
</div>
);
});
export default LogsViewer;
-178
View File
@@ -1,178 +0,0 @@
import useSetChunkRequest from '@/hooks/use-chunk-request';
import { FitAddon } from '@xterm/addon-fit';
import { Terminal } from '@xterm/xterm';
import '@xterm/xterm/css/xterm.css';
import classNames from 'classnames';
import _ from 'lodash';
import {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState
} from 'react';
import { replaceLineRegex } from './config';
import './styles/xterm-viewer.less';
import useSize from './use-size';
interface LogsViewerProps {
height: number;
content?: string;
url: string;
ref?: any;
params?: object;
}
const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
const { height, content, url } = props;
const { setChunkRequest } = useSetChunkRequest();
const chunkRequedtRef = useRef<any>(null);
const scroller = useRef<any>({});
const termRef = useRef<any>({});
const termwrapRef = useRef<any>({});
const fitAddonRef = useRef<any>({});
const cacheDataRef = useRef<any>(null);
const [logs, setLogs] = useState('');
const [loading, setLoading] = useState(false);
const size = useSize(scroller);
const logParseWorker = useRef<any>(null);
const lineCountRef = useRef(0);
useImperativeHandle(ref, () => ({
abort() {
chunkRequedtRef.current?.current?.abort?.();
logParseWorker.current?.terminate?.();
}
}));
useEffect(() => {
logParseWorker.current?.terminate?.();
logParseWorker.current = new Worker(
// @ts-ignore
new URL('./parse-worker.ts', import.meta.url),
{
type: 'module'
}
);
logParseWorker.current.onmessage = (event: any) => {
const { result, lines } = event.data;
lineCountRef.current = lines;
// setLogs(result.join('\n'));
console.log('res+++++++++++', result);
const data = result.map((item: any) => item.content);
termRef.current?.write?.(data.join('\n'));
};
return () => {
if (logParseWorker.current) {
logParseWorker.current.terminate();
}
};
}, []);
const throttleScroll = _.throttle(() => {
termRef.current?.scrollToBottom?.();
}, 100);
const debounceLoading = _.debounce(() => {
setLoading(false);
}, 200);
const updateContent = (inputStr: string) => {
const data = inputStr.replace(replaceLineRegex, '\n');
cacheDataRef.current = data;
setLoading(true);
logParseWorker.current.postMessage({
inputStr: data
});
debounceLoading();
};
const fitTerm = () => {
fitAddonRef.current?.fit?.();
};
const createChunkConnection = async () => {
chunkRequedtRef.current?.current?.cancel?.();
chunkRequedtRef.current = setChunkRequest({
url,
params: {
...props.params,
watch: true
},
contentType: 'text',
handler: updateContent
});
};
const initTerm = () => {
termRef.current?.dispose?.();
termRef.current = new Terminal({
lineHeight: 1.2,
fontSize: 13,
fontFamily:
"monospace,Menlo,Courier,'Courier New',Consolas,Monaco, 'Liberation Mono'",
disableStdin: true,
convertEol: true,
theme: {
background: '#1e1e1e',
foreground: 'rgba(255,255,255,0.8)'
},
cursorInactiveStyle: 'none',
smoothScrollDuration: 0
});
fitAddonRef.current = new FitAddon();
termRef.current.loadAddon(fitAddonRef.current);
termRef.current.open(termwrapRef.current);
// add event
// termRef.current.onLineFeed((e: any) => {
// if (cacheDataRef.current) {
// throttleScroll();
// }
// });
};
const handleResize = _.throttle(() => {
fitTerm();
}, 100);
useEffect(() => {
createChunkConnection();
return () => {
chunkRequedtRef.current?.current?.cancel?.();
};
}, [url, props.params]);
useEffect(() => {
if (termwrapRef.current) {
initTerm();
}
return () => {
termRef.current?.dispose?.();
};
}, [termwrapRef.current]);
useEffect(() => {
if (size) {
handleResize();
}
}, [size]);
useEffect(() => {
// throttleScroll();
}, [logs]);
return (
<div className="logs-viewer-wrap-w2">
<div className="wrap" style={{ height: height }} ref={scroller}>
<div className={classNames('content')}>
<div className="text" ref={termwrapRef}></div>
</div>
</div>
</div>
);
});
export default LogsViewer;