mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-09-12 09:34:58 +08:00
feat: 网站日志支持读取 10M 以上文件 (#2072)
This commit is contained in:
parent
680e93dcd4
commit
9f0d957145
6 changed files with 103 additions and 33 deletions
|
@ -141,9 +141,11 @@ type WebsiteNginxUpdate struct {
|
|||
}
|
||||
|
||||
type WebsiteLogReq struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
LogType string `json:"logType" validate:"required"`
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
LogType string `json:"logType" validate:"required"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
type WebsiteDefaultUpdate struct {
|
||||
|
|
|
@ -41,6 +41,7 @@ type WebsiteHTTPS struct {
|
|||
type WebsiteLog struct {
|
||||
Enable bool `json:"enable"`
|
||||
Content string `json:"content"`
|
||||
End bool `json:"end"`
|
||||
}
|
||||
|
||||
type PHPConfig struct {
|
||||
|
|
|
@ -931,19 +931,12 @@ func (w WebsiteService) OpWebsiteLog(req request.WebsiteLogReq) (*response.Websi
|
|||
}
|
||||
}
|
||||
filePath := path.Join(sitePath, "log", req.LogType)
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
lines, end, err := files.ReadFileByLine(filePath, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fileInfo.Size() > 20<<20 {
|
||||
return nil, buserr.New(constant.ErrFileToLarge)
|
||||
}
|
||||
fileInfo.Size()
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Content = string(content)
|
||||
res.End = end
|
||||
res.Content = strings.Join(lines, "\n")
|
||||
return res, nil
|
||||
case constant.DisableLog:
|
||||
key := "access_log"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package files
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/spf13/afero"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -75,3 +76,36 @@ const dotCharacter = 46
|
|||
func IsHidden(path string) bool {
|
||||
return path[0] == dotCharacter
|
||||
}
|
||||
|
||||
func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
var lines []string
|
||||
currentLine := 0
|
||||
startLine := (page - 1) * pageSize
|
||||
endLine := startLine + pageSize
|
||||
|
||||
for scanner.Scan() {
|
||||
if currentLine >= startLine && currentLine < endLine {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
currentLine++
|
||||
if currentLine >= endLine {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
isEndOfFile := currentLine < endLine
|
||||
|
||||
return lines, isEndOfFile, nil
|
||||
}
|
||||
|
|
|
@ -82,11 +82,14 @@ export namespace Website {
|
|||
id: number;
|
||||
operate: string;
|
||||
logType: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface WebSiteLog {
|
||||
enable: boolean;
|
||||
content: string;
|
||||
end: boolean;
|
||||
}
|
||||
|
||||
export interface Domain {
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
</div>
|
||||
<br />
|
||||
<codemirror
|
||||
re="logContainer"
|
||||
style="height: calc(100vh - 430px); width: 100%"
|
||||
:autofocus="true"
|
||||
:placeholder="$t('website.noLog')"
|
||||
|
@ -33,7 +34,7 @@
|
|||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="data.content"
|
||||
v-model="content"
|
||||
:disabled="true"
|
||||
@ready="handleReady"
|
||||
/>
|
||||
|
@ -43,7 +44,7 @@
|
|||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, shallowRef } from 'vue';
|
||||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, shallowRef } from 'vue';
|
||||
import { OpWebsiteLog } from '@/api/modules/website';
|
||||
import { dateFormatForName, downloadWithContent } from '@/utils/util';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
|
@ -74,31 +75,52 @@ const tailLog = ref(false);
|
|||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
const view = shallowRef();
|
||||
const editorContainer = ref<HTMLDivElement | null>(null);
|
||||
const handleReady = (payload) => {
|
||||
view.value = payload.view;
|
||||
editorContainer.value = payload.container;
|
||||
};
|
||||
const content = ref('');
|
||||
const end = ref(false);
|
||||
const lastContent = ref('');
|
||||
|
||||
const readReq = reactive({
|
||||
id: id.value,
|
||||
operate: 'get',
|
||||
logType: logType.value,
|
||||
page: 0,
|
||||
pageSize: 500,
|
||||
});
|
||||
|
||||
const getContent = () => {
|
||||
const req = {
|
||||
id: id.value,
|
||||
operate: 'get',
|
||||
logType: logType.value,
|
||||
};
|
||||
loading.value = true;
|
||||
OpWebsiteLog(req)
|
||||
.then((res) => {
|
||||
data.value = res.data;
|
||||
nextTick(() => {
|
||||
const state = view.value.state;
|
||||
view.value.dispatch({
|
||||
selection: { anchor: state.doc.length, head: state.doc.length },
|
||||
scrollIntoView: true,
|
||||
});
|
||||
if (!end.value) {
|
||||
readReq.page += 1;
|
||||
}
|
||||
OpWebsiteLog(readReq).then((res) => {
|
||||
if (!end.value && res.data.end) {
|
||||
lastContent.value = content.value;
|
||||
}
|
||||
data.value = res.data;
|
||||
if (res.data.content != '') {
|
||||
if (end.value) {
|
||||
content.value = lastContent.value + '\n' + res.data.content;
|
||||
} else {
|
||||
if (content.value == '') {
|
||||
content.value = res.data.content;
|
||||
} else {
|
||||
content.value = content.value + '\n' + res.data.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
end.value = res.data.end;
|
||||
nextTick(() => {
|
||||
const state = view.value.state;
|
||||
view.value.dispatch({
|
||||
selection: { anchor: state.doc.length, head: state.doc.length },
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
view.value.focus();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const changeTail = () => {
|
||||
|
@ -152,8 +174,23 @@ const onCloseLog = async () => {
|
|||
timer = null;
|
||||
};
|
||||
|
||||
function isScrolledToBottom(element: HTMLElement): boolean {
|
||||
return element.scrollTop + element.clientHeight === element.scrollHeight;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getContent();
|
||||
nextTick(() => {
|
||||
let editorElement = editorContainer.value.querySelector('.cm-editor');
|
||||
let scrollerElement = editorElement.querySelector('.cm-scroller') as HTMLElement;
|
||||
if (scrollerElement) {
|
||||
scrollerElement.addEventListener('scroll', function () {
|
||||
if (isScrolledToBottom(scrollerElement)) {
|
||||
getContent();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
|
Loading…
Add table
Reference in a new issue