diff --git a/backend/app/dto/response/file.go b/backend/app/dto/response/file.go index 288260800..977f7b148 100644 --- a/backend/app/dto/response/file.go +++ b/backend/app/dto/response/file.go @@ -36,10 +36,11 @@ type FileWgetRes struct { } type FileLineContent struct { - Content string `json:"content"` - End bool `json:"end"` - Path string `json:"path"` - Total int `json:"total"` + Content string `json:"content"` + End bool `json:"end"` + Path string `json:"path"` + Total int `json:"total"` + Lines []string `json:"lines"` } type FileExist struct { diff --git a/backend/app/service/file.go b/backend/app/service/file.go index b48191fc6..ace3787eb 100644 --- a/backend/app/service/file.go +++ b/backend/app/service/file.go @@ -474,11 +474,20 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi if err != nil { return nil, err } + if req.Latest && req.Page == 1 && len(lines) < 1000 && total > 1 { + preLines, _, _, err := files.ReadFileByLine(logFilePath, total-1, req.PageSize, false) + if err != nil { + return nil, err + } + lines = append(preLines, lines...) + } + res := &response.FileLineContent{ Content: strings.Join(lines, "\n"), End: isEndOfFile, Path: logFilePath, Total: total, + Lines: lines, } return res, nil } diff --git a/frontend/src/components/log-file/index.vue b/frontend/src/components/log-file/index.vue index 14f3161ad..becbee84d 100644 --- a/frontend/src/components/log-file/index.vue +++ b/frontend/src/components/log-file/index.vue @@ -29,7 +29,6 @@ import { ReadByLine } from '@/api/modules/files'; import { watch } from 'vue'; const editorRef = ref(); - interface LogProps { id?: number; type: string; @@ -74,10 +73,11 @@ let timer: NodeJS.Timer | null = null; const tailLog = ref(false); const content = ref(''); const end = ref(false); -const lastContent = ref(''); const scrollerElement = ref(null); const minPage = ref(1); const maxPage = ref(1); +const logs = ref([]); +const isLoading = ref(false); const readReq = reactive({ id: 0, @@ -114,7 +114,12 @@ const stopSignals = [ 'image push successful!', ]; +const lastLogs = ref([]); + const getContent = (pre: boolean) => { + if (isLoading.value) { + return; + } emit('update:isReading', true); readReq.id = props.config.id; readReq.type = props.config.type; @@ -122,37 +127,52 @@ const getContent = (pre: boolean) => { if (readReq.page < 1) { readReq.page = 1; } + isLoading.value = true; ReadByLine(readReq).then((res) => { if (!end.value && res.data.end) { - lastContent.value = content.value; + lastLogs.value = [...logs.value]; } - res.data.content = res.data.content.replace(/\\u(\w{4})/g, function (match, grp) { - return String.fromCharCode(parseInt(grp, 16)); - }); data.value = res.data; - if (res.data.content != '') { - if (stopSignals.some((signal) => res.data.content.endsWith(signal))) { + if (res.data.lines && res.data.lines.length > 0) { + res.data.lines = res.data.lines.map((line) => + line.replace(/\\u(\w{4})/g, function (match, grp) { + return String.fromCharCode(parseInt(grp, 16)); + }), + ); + const newLogs = res.data.lines; + if (newLogs.length === readReq.pageSize && readReq.page < res.data.total) { + readReq.page++; + } + + if ( + readReq.type == 'php' && + logs.value.length > 0 && + newLogs.length > 0 && + newLogs[newLogs.length - 1] === logs.value[logs.value.length - 1] + ) { + isLoading.value = false; + return; + } + + if (stopSignals.some((signal) => newLogs[newLogs.length - 1].endsWith(signal))) { onCloseLog(); } if (end.value) { - if (lastContent.value == '') { - content.value = res.data.content; + if ((logs.value.length = 0)) { + logs.value = newLogs; } else { - content.value = pre - ? res.data.content + '\n' + lastContent.value - : lastContent.value + '\n' + res.data.content; + logs.value = pre ? [...newLogs, ...lastLogs.value] : [...lastLogs.value, ...newLogs]; } } else { - if (content.value == '') { - content.value = res.data.content; + if ((logs.value.length = 0)) { + logs.value = newLogs; } else { - content.value = pre - ? res.data.content + '\n' + content.value - : content.value + '\n' + res.data.content; + logs.value = pre ? [...newLogs, ...logs.value] : [...logs.value, ...newLogs]; } } } + end.value = res.data.end; emit('update:hasContent', content.value !== ''); nextTick(() => { @@ -171,16 +191,53 @@ const getContent = (pre: boolean) => { maxPage.value = res.data.total; minPage.value = res.data.total; } + if (logs.value && logs.value.length > 3000) { + logs.value.splice(0, readReq.pageSize); + if (minPage.value > 1) { + minPage.value--; + } + } + + isLoading.value = false; + content.value = logs.value.join('\n'); }); }; +function throttle any>(func: T, limit: number): (...args: Parameters) => void { + let inThrottle: boolean; + let lastFunc: ReturnType; + let lastRan: number; + return function (this: any, ...args: Parameters) { + if (!inThrottle) { + func.apply(this, args); + lastRan = Date.now(); + inThrottle = true; + setTimeout(() => (inThrottle = false), limit); + } else { + clearTimeout(lastFunc); + lastFunc = setTimeout(() => { + if (Date.now() - lastRan >= limit) { + func.apply(this, args); + lastRan = Date.now(); + } + }, limit - (Date.now() - lastRan)); + } + }; +} + +const throttledGetContent = throttle(getContent, 3000); + +const search = () => { + throttledGetContent(false); +}; + const changeTail = (fromOutSide: boolean) => { if (fromOutSide) { tailLog.value = !tailLog.value; } if (tailLog.value) { timer = setInterval(() => { - getContent(false); + search(); }, 1000 * 3); } else { onCloseLog(); @@ -198,6 +255,7 @@ const onCloseLog = async () => { tailLog.value = false; clearInterval(Number(timer)); timer = null; + isLoading.value = false; }; function isScrolledToBottom(element: HTMLElement): boolean { @@ -218,7 +276,7 @@ const init = () => { changeTail(false); } readReq.latest = true; - getContent(false); + search(); nextTick(() => {}); }; @@ -232,9 +290,14 @@ const initCodemirror = () => { if (editorRef.value) { scrollerElement.value = editorRef.value.$el as HTMLElement; scrollerElement.value.addEventListener('scroll', function () { + if (tailLog.value) { + return; + } if (isScrolledToBottom(scrollerElement.value)) { - readReq.page = maxPage.value; - getContent(false); + if (maxPage.value > 1) { + readReq.page = maxPage.value; + } + search(); } if (isScrolledToTop(scrollerElement.value)) { readReq.page = minPage.value - 1; @@ -242,7 +305,7 @@ const initCodemirror = () => { return; } minPage.value = readReq.page; - getContent(true); + throttledGetContent(true); } }); let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;