169 lines
4.4 KiB
Vue
169 lines
4.4 KiB
Vue
<template>
|
||
<div class="app-container">
|
||
<el-card>
|
||
<div slot="header" class="clearfix">
|
||
<span>日志文件查看</span>
|
||
<span class="hint">通过 HTTPS 轮询读取最新内容,无需 SSH</span>
|
||
</div>
|
||
<el-form :inline="true" size="small" class="toolbar">
|
||
<el-form-item label="日志文件">
|
||
<el-select v-model="currentFile" placeholder="请选择" style="width: 180px" @change="handleFileChange">
|
||
<el-option
|
||
v-for="f in fileList"
|
||
:key="f"
|
||
:label="f"
|
||
:value="f"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="行数">
|
||
<el-input-number v-model="lines" :min="100" :max="5000" :step="100" style="width: 120px" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" icon="el-icon-refresh" :loading="loading" @click="fetchLog">刷新</el-button>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-checkbox v-model="autoRefresh">自动刷新</el-checkbox>
|
||
</el-form-item>
|
||
<el-form-item v-if="autoRefresh">
|
||
<el-select v-model="refreshInterval" style="width: 100px" @change="restartTimer">
|
||
<el-option label="5 秒" :value="5" />
|
||
<el-option label="10 秒" :value="10" />
|
||
<el-option label="30 秒" :value="30" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div v-if="infoText" class="info-bar">
|
||
{{ infoText }}
|
||
</div>
|
||
<div class="log-wrap">
|
||
<pre ref="logPre" class="log-content">{{ logContent }}</pre>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { listLogfiles, tailLogfile } from '@/api/monitor/logfile'
|
||
|
||
export default {
|
||
name: 'LogfileViewer',
|
||
data() {
|
||
return {
|
||
fileList: [],
|
||
currentFile: 'all.log',
|
||
lines: 200,
|
||
loading: false,
|
||
logContent: '',
|
||
totalLines: 0,
|
||
fromLine: 0,
|
||
toLine: 0,
|
||
autoRefresh: false,
|
||
refreshInterval: 10,
|
||
timer: null
|
||
}
|
||
},
|
||
computed: {
|
||
infoText() {
|
||
if (!this.currentFile || !this.totalLines) return ''
|
||
return `文件: ${this.currentFile} | 显示第 ${this.fromLine} - ${this.toLine} 行,共 ${this.totalLines} 行`
|
||
}
|
||
},
|
||
created() {
|
||
this.loadFileList()
|
||
},
|
||
beforeDestroy() {
|
||
this.stopTimer()
|
||
},
|
||
methods: {
|
||
loadFileList() {
|
||
listLogfiles().then(res => {
|
||
const list = res.data || []
|
||
this.fileList = Array.isArray(list) ? list : []
|
||
if (this.fileList.length && !this.fileList.includes(this.currentFile)) {
|
||
this.currentFile = this.fileList[0]
|
||
}
|
||
this.fetchLog()
|
||
}).catch(() => {
|
||
this.$message.error('获取日志列表失败')
|
||
})
|
||
},
|
||
handleFileChange() {
|
||
this.fetchLog()
|
||
},
|
||
fetchLog() {
|
||
if (!this.currentFile) return
|
||
this.loading = true
|
||
tailLogfile(this.currentFile, this.lines).then(res => {
|
||
const d = res.data || {}
|
||
this.logContent = d.content != null ? d.content : ''
|
||
this.totalLines = d.totalLines || 0
|
||
this.fromLine = d.fromLine || 0
|
||
this.toLine = d.toLine || 0
|
||
this.$nextTick(() => this.scrollToBottom())
|
||
}).catch(e => {
|
||
this.$message.error(e.msg || '读取日志失败')
|
||
this.logContent = ''
|
||
}).finally(() => {
|
||
this.loading = false
|
||
})
|
||
},
|
||
scrollToBottom() {
|
||
const el = this.$refs.logPre
|
||
if (el) el.scrollTop = el.scrollHeight
|
||
},
|
||
startTimer() {
|
||
this.stopTimer()
|
||
this.timer = setInterval(() => this.fetchLog(), this.refreshInterval * 1000)
|
||
},
|
||
stopTimer() {
|
||
if (this.timer) {
|
||
clearInterval(this.timer)
|
||
this.timer = null
|
||
}
|
||
},
|
||
restartTimer() {
|
||
if (this.autoRefresh) this.startTimer()
|
||
}
|
||
},
|
||
watch: {
|
||
autoRefresh(v) {
|
||
if (v) this.startTimer()
|
||
else this.stopTimer()
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.hint {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-left: 12px;
|
||
}
|
||
.toolbar {
|
||
margin-bottom: 12px;
|
||
}
|
||
.info-bar {
|
||
font-size: 12px;
|
||
color: #606266;
|
||
margin-bottom: 8px;
|
||
}
|
||
.log-wrap {
|
||
background: #1e1e1e;
|
||
border-radius: 4px;
|
||
padding: 12px;
|
||
max-height: 70vh;
|
||
overflow: auto;
|
||
}
|
||
.log-content {
|
||
margin: 0;
|
||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
color: #d4d4d4;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
</style>
|