Skip to content

Commit 345dbda

Browse files
feat: 优化日志读取 (#5485)
Refs #3690
1 parent f4103e2 commit 345dbda

File tree

9 files changed

+119
-85
lines changed

9 files changed

+119
-85
lines changed

backend/app/dto/request/file.go

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ type FileReadByLineReq struct {
126126
Type string `json:"type" validate:"required"`
127127
ID uint `json:"ID"`
128128
Name string `json:"name"`
129+
Latest bool `json:"latest"`
129130
}
130131

131132
type FileExistReq struct {

backend/app/dto/response/file.go

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type FileLineContent struct {
3737
Content string `json:"content"`
3838
End bool `json:"end"`
3939
Path string `json:"path"`
40+
Total int `json:"total"`
4041
}
4142

4243
type FileExist struct {

backend/app/service/file.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -410,14 +410,15 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
410410
logFilePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name))
411411
}
412412

413-
lines, isEndOfFile, err := files.ReadFileByLine(logFilePath, req.Page, req.PageSize)
413+
lines, isEndOfFile, total, err := files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest)
414414
if err != nil {
415415
return nil, err
416416
}
417417
res := &response.FileLineContent{
418418
Content: strings.Join(lines, "\n"),
419419
End: isEndOfFile,
420420
Path: logFilePath,
421+
Total: total,
421422
}
422423
return res, nil
423424
}

backend/app/service/website.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ func (w WebsiteService) OpWebsiteLog(req request.WebsiteLogReq) (*response.Websi
10081008
}
10091009
}
10101010
filePath := path.Join(sitePath, "log", req.LogType)
1011-
lines, end, err := files.ReadFileByLine(filePath, req.Page, req.PageSize)
1011+
lines, end, _, err := files.ReadFileByLine(filePath, req.Page, req.PageSize, false)
10121012
if err != nil {
10131013
return nil, err
10141014
}

backend/utils/files/utils.go

+31-7
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,44 @@ func IsHidden(path string) bool {
6666
return path[0] == dotCharacter
6767
}
6868

69-
func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error) {
69+
func countLines(path string) (int, error) {
70+
file, err := os.Open(path)
71+
if err != nil {
72+
return 0, err
73+
}
74+
defer file.Close()
75+
76+
scanner := bufio.NewScanner(file)
77+
lineCount := 0
78+
for scanner.Scan() {
79+
lineCount++
80+
}
81+
if err := scanner.Err(); err != nil {
82+
return 0, err
83+
}
84+
return lineCount, nil
85+
}
86+
87+
func ReadFileByLine(filename string, page, pageSize int, latest bool) (lines []string, isEndOfFile bool, total int, err error) {
7088
if !NewFileOp().Stat(filename) {
71-
return nil, true, nil
89+
return
7290
}
7391
file, err := os.Open(filename)
7492
if err != nil {
75-
return nil, false, err
93+
return
7694
}
7795
defer file.Close()
7896

97+
totalLines, err := countLines(filename)
98+
if err != nil {
99+
return
100+
}
101+
total = (totalLines + pageSize - 1) / pageSize
79102
reader := bufio.NewReaderSize(file, 8192)
80103

81-
var lines []string
104+
if latest {
105+
page = total
106+
}
82107
currentLine := 0
83108
startLine := (page - 1) * pageSize
84109
endLine := startLine + pageSize
@@ -97,9 +122,8 @@ func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error)
97122
}
98123
}
99124

100-
isEndOfFile := currentLine < endLine
101-
102-
return lines, isEndOfFile, nil
125+
isEndOfFile = currentLine < endLine
126+
return
103127
}
104128

105129
func GetParentMode(path string) (os.FileMode, error) {

frontend/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "1Panel-Frontend",
33
"private": true,
4-
"version": "1.7",
4+
"version": "1.10",
55
"description": "1Panel 前端",
66
"scripts": {
77
"dev": "vite",
@@ -25,13 +25,15 @@
2525
"@codemirror/legacy-modes": "^6.4.0",
2626
"@codemirror/theme-one-dark": "^6.1.2",
2727
"@element-plus/icons-vue": "^1.1.4",
28+
"@highlightjs/vue-plugin": "^2.1.0",
2829
"@vueuse/core": "^8.9.4",
2930
"@xterm/addon-fit": "^0.10.0",
3031
"@xterm/xterm": "^5.5.0",
3132
"axios": "^1.7.2",
3233
"echarts": "^5.5.0",
3334
"element-plus": "^2.7.5",
3435
"fit2cloud-ui-plus": "^1.1.4",
36+
"highlight.js": "^11.9.0",
3537
"js-base64": "^3.7.7",
3638
"md-editor-v3": "^2.11.3",
3739
"monaco-editor": "^0.34.1",

frontend/src/components/log-file/index.vue

+75-74
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,23 @@
1212
</span>
1313
</div>
1414
<div class="mt-2.5">
15-
<Codemirror
16-
ref="logContainer"
17-
:style="styleObject"
18-
:autofocus="true"
19-
:placeholder="$t('website.noLog')"
20-
:indent-with-tab="true"
21-
:tabSize="4"
22-
:lineWrapping="true"
23-
:matchBrackets="true"
24-
theme="cobalt"
25-
:styleActiveLine="true"
26-
:extensions="extensions"
27-
v-model="content"
28-
:disabled="true"
29-
@ready="handleReady"
30-
/>
15+
<highlightjs
16+
ref="editorRef"
17+
class="editor-main"
18+
language="JavaScript"
19+
:autodetect="false"
20+
:code="content"
21+
></highlightjs>
3122
</div>
3223
</div>
3324
</template>
3425
<script lang="ts" setup>
35-
import { Codemirror } from 'vue-codemirror';
36-
import { javascript } from '@codemirror/lang-javascript';
37-
import { oneDark } from '@codemirror/theme-one-dark';
38-
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, shallowRef } from 'vue';
26+
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
3927
import { downloadFile } from '@/utils/util';
4028
import { ReadByLine } from '@/api/modules/files';
4129
import { watch } from 'vue';
4230
43-
const extensions = [javascript(), oneDark];
31+
const editorRef = ref();
4432
4533
interface LogProps {
4634
id?: number;
@@ -61,7 +49,7 @@ const props = defineProps({
6149
},
6250
style: {
6351
type: String,
64-
default: 'height: calc(100vh - 200px); width: 100%; min-height: 400px',
52+
default: 'height: calc(100vh - 200px); width: 100%; min-height: 400px; overflow: auto;',
6553
},
6654
defaultButton: {
6755
type: Boolean,
@@ -84,29 +72,23 @@ const data = ref({
8472
8573
let timer: NodeJS.Timer | null = null;
8674
const tailLog = ref(false);
87-
const view = shallowRef();
8875
const content = ref('');
8976
const end = ref(false);
9077
const lastContent = ref('');
91-
const logContainer = ref();
9278
const scrollerElement = ref<HTMLElement | null>(null);
79+
const minPage = ref(1);
80+
const maxPage = ref(1);
9381
9482
const readReq = reactive({
9583
id: 0,
9684
type: '',
9785
name: '',
98-
page: 0,
99-
pageSize: 2000,
86+
page: 1,
87+
pageSize: 500,
88+
latest: false,
10089
});
10190
const emit = defineEmits(['update:loading', 'update:hasContent', 'update:isReading']);
10291
103-
const handleReady = (payload) => {
104-
view.value = payload.view;
105-
const editorContainer = payload.container;
106-
const editorElement = editorContainer.querySelector('.cm-editor');
107-
scrollerElement.value = editorElement.querySelector('.cm-scroller') as HTMLElement;
108-
};
109-
11092
const loading = ref(props.loading);
11193
11294
watch(
@@ -121,25 +103,6 @@ const changeLoading = () => {
121103
emit('update:loading', loading.value);
122104
};
123105
124-
const styleObject = computed(() => {
125-
const styles = {};
126-
let style = 'height: calc(100vh - 200px); width: 100%; min-height: 400px';
127-
if (props.style != null && props.style != '') {
128-
style = props.style;
129-
}
130-
style.split(';').forEach((styleRule) => {
131-
const [property, value] = styleRule.split(':');
132-
if (property && value) {
133-
const formattedProperty = property
134-
.trim()
135-
.replace(/([a-z])([A-Z])/g, '$1-$2')
136-
.toLowerCase();
137-
styles[formattedProperty] = value.trim();
138-
}
139-
});
140-
return styles;
141-
});
142-
143106
const stopSignals = [
144107
'docker-compose up failed!',
145108
'docker-compose up successful!',
@@ -151,18 +114,16 @@ const stopSignals = [
151114
'image push successful!',
152115
];
153116
154-
const getContent = () => {
117+
const getContent = (pre: boolean) => {
155118
emit('update:isReading', true);
156-
if (!end.value) {
157-
readReq.page += 1;
158-
}
159119
readReq.id = props.config.id;
160120
readReq.type = props.config.type;
161121
readReq.name = props.config.name;
162122
ReadByLine(readReq).then((res) => {
163123
if (!end.value && res.data.end) {
164124
lastContent.value = content.value;
165125
}
126+
166127
res.data.content = res.data.content.replace(/\\u(\w{4})/g, function (match, grp) {
167128
return String.fromCharCode(parseInt(grp, 16));
168129
});
@@ -175,28 +136,38 @@ const getContent = () => {
175136
if (lastContent.value == '') {
176137
content.value = res.data.content;
177138
} else {
178-
content.value = lastContent.value + '\n' + res.data.content;
139+
content.value = pre
140+
? res.data.content + '\n' + lastContent.value
141+
: lastContent.value + '\n' + res.data.content;
179142
}
180143
} else {
181144
if (content.value == '') {
182145
content.value = res.data.content;
183146
} else {
184-
content.value = content.value + '\n' + res.data.content;
147+
content.value = pre
148+
? res.data.content + '\n' + content.value
149+
: content.value + '\n' + res.data.content;
185150
}
186151
}
187152
}
188153
end.value = res.data.end;
189154
emit('update:hasContent', content.value !== '');
190155
nextTick(() => {
191-
const state = view.value.state;
192-
view.value.dispatch({
193-
selection: { anchor: state.doc.length, head: state.doc.length },
194-
});
195-
view.value.focus();
196-
const firstLine = view.value.state.doc.line(view.value.state.doc.lines);
197-
const { top } = view.value.lineBlockAt(firstLine.from);
198-
scrollerElement.value.scrollTo({ top, behavior: 'instant' });
156+
if (pre) {
157+
if (scrollerElement.value.scrollHeight > 2000) {
158+
scrollerElement.value.scrollTop = 2000;
159+
}
160+
} else {
161+
scrollerElement.value.scrollTop = scrollerElement.value.scrollHeight;
162+
}
199163
});
164+
165+
if (readReq.latest) {
166+
readReq.page = res.data.total;
167+
readReq.latest = false;
168+
maxPage.value = res.data.total;
169+
minPage.value = res.data.total;
170+
}
200171
});
201172
};
202173
@@ -206,7 +177,7 @@ const changeTail = (fromOutSide: boolean) => {
206177
}
207178
if (tailLog.value) {
208179
timer = setInterval(() => {
209-
getContent();
180+
getContent(false);
210181
}, 1000 * 2);
211182
} else {
212183
onCloseLog();
@@ -230,6 +201,10 @@ function isScrolledToBottom(element: HTMLElement): boolean {
230201
return element.scrollTop + element.clientHeight + 1 >= element.scrollHeight;
231202
}
232203
204+
function isScrolledToTop(element: HTMLElement): boolean {
205+
return element.scrollTop === 0;
206+
}
207+
233208
const init = () => {
234209
if (props.config.tail) {
235210
tailLog.value = props.config.tail;
@@ -239,30 +214,56 @@ const init = () => {
239214
if (tailLog.value) {
240215
changeTail(false);
241216
}
242-
getContent();
217+
readReq.latest = true;
218+
getContent(false);
219+
220+
nextTick(() => {});
221+
};
222+
223+
const clearLog = (): void => {
224+
content.value = '';
225+
};
243226
227+
const initCodemirror = () => {
244228
nextTick(() => {
245-
if (scrollerElement.value) {
229+
if (editorRef.value) {
230+
scrollerElement.value = editorRef.value.$el as HTMLElement;
246231
scrollerElement.value.addEventListener('scroll', function () {
247232
if (isScrolledToBottom(scrollerElement.value)) {
248-
getContent();
233+
readReq.page = maxPage.value;
234+
getContent(false);
235+
}
236+
if (isScrolledToTop(scrollerElement.value)) {
237+
readReq.page = minPage.value - 1;
238+
if (readReq.page < 1) {
239+
return;
240+
}
241+
minPage.value = readReq.page;
242+
getContent(true);
249243
}
250244
});
245+
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;
246+
hljsDom.style['min-height'] = '300px';
251247
}
252248
});
253249
};
254250
255-
const clearLog = (): void => {
256-
content.value = '';
257-
};
258-
259251
onUnmounted(() => {
260252
onCloseLog();
261253
});
262254
263255
onMounted(() => {
256+
initCodemirror();
264257
init();
265258
});
266259
267260
defineExpose({ changeTail, onDownload, clearLog });
268261
</script>
262+
<style lang="scss" scoped>
263+
.editor-main {
264+
height: calc(100vh - 480px);
265+
width: 100%;
266+
min-height: 400px;
267+
overflow: auto;
268+
}
269+
</style>

0 commit comments

Comments
 (0)