2025-04-28 12:25:20 +08:00
|
|
|
|
class RunLog extends HTMLElement {
|
|
|
|
|
constructor() {
|
|
|
|
|
super();
|
|
|
|
|
this.attachShadow({ mode: 'open' });
|
|
|
|
|
this.logFiles = [];
|
|
|
|
|
this.expandedLogs = new Set();
|
|
|
|
|
this.logDir = '/log'; // 直接设置日志目录路径
|
2025-05-07 14:49:02 +08:00
|
|
|
|
// 添加分页相关的状态变量
|
|
|
|
|
this.currentPage = 1;
|
|
|
|
|
this.pageSize = 10; // 每页显示10个日志文件
|
|
|
|
|
this.totalPages = 1;
|
|
|
|
|
this.currentLogContent = []; // 存储当前日志文件的所有内容
|
2025-04-28 12:25:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
connectedCallback() {
|
|
|
|
|
this.render();
|
|
|
|
|
this.setupEventListeners();
|
|
|
|
|
this.loadLogFileList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
this.shadowRoot.innerHTML = `
|
|
|
|
|
<style>
|
|
|
|
|
:host {
|
|
|
|
|
display: block;
|
|
|
|
|
height: 100%;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.run-log-container {
|
|
|
|
|
background-color: white;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
|
|
|
padding: 16px;
|
|
|
|
|
height: 100%;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-list {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-item {
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-item-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-item-info {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-item-title {
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-item-time {
|
|
|
|
|
color: #666;
|
|
|
|
|
font-size: 0.9em;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-28 16:20:01 +08:00
|
|
|
|
.log-item-source {
|
|
|
|
|
color: #888;
|
|
|
|
|
font-size: 0.8em;
|
|
|
|
|
margin-top: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-28 12:25:20 +08:00
|
|
|
|
.log-item-toggle {
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
color: #333;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
padding: 4px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: background-color 0.3s;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
width: 32px;
|
|
|
|
|
height: 32px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-item-toggle img {
|
|
|
|
|
width: 16px;
|
|
|
|
|
height: 16px;
|
|
|
|
|
transition: transform 0.3s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-item-toggle:hover {
|
|
|
|
|
background-color: rgba(0,0,0,0.05);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-item.expanded .log-item-toggle {
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-item-content {
|
|
|
|
|
display: none;
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
padding: 0;
|
|
|
|
|
margin: 0;
|
|
|
|
|
font-family: monospace;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
color: #333;
|
|
|
|
|
max-height: 300px;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 清除所有在contentElement中可能出现的空白 */
|
|
|
|
|
.log-item-content > * {
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-item.expanded .log-item-content {
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-filters {
|
|
|
|
|
display: flex;
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
background-color: #eaeaea;
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
border-bottom: 1px solid #ddd;
|
|
|
|
|
position: sticky;
|
|
|
|
|
top: 0;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
line-height: normal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-filter {
|
|
|
|
|
padding: 4px 12px;
|
|
|
|
|
margin: 0;
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
user-select: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-filter.active {
|
|
|
|
|
background-color: #8B6DB3;
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-content-messages {
|
|
|
|
|
padding: 8px;
|
|
|
|
|
margin: 0;
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-message {
|
|
|
|
|
margin: 2px 0;
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
border-bottom: 1px solid #e8e8e8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-message.info {
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-message.warning {
|
|
|
|
|
color: #faad14;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-message.error {
|
|
|
|
|
color: #f5222d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.no-logs {
|
|
|
|
|
text-align: center;
|
|
|
|
|
padding: 32px;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
2025-05-07 14:49:02 +08:00
|
|
|
|
|
|
|
|
|
.pagination {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
border-top: 1px solid #e0e0e0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pagination-button {
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
border: 1px solid #d9d9d9;
|
|
|
|
|
background-color: white;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pagination-button:hover {
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pagination-button:disabled {
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
color: #d9d9d9;
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pagination-info {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pagination-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
margin-left: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-action-button {
|
|
|
|
|
padding: 6px;
|
|
|
|
|
border: 1px solid #d9d9d9;
|
|
|
|
|
background-color: white;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
width: 32px;
|
|
|
|
|
height: 32px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-action-button img {
|
|
|
|
|
width: 20px;
|
|
|
|
|
height: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.log-action-button:hover {
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
}
|
2025-04-28 12:25:20 +08:00
|
|
|
|
</style>
|
|
|
|
|
<div class="run-log-container">
|
|
|
|
|
<div class="log-list" id="logList">
|
|
|
|
|
<div class="no-logs">暂无日志记录</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupEventListeners() {
|
2025-05-07 14:49:02 +08:00
|
|
|
|
// 移除顶部刷新按钮的事件监听,因为按钮已经被移除
|
2025-04-28 12:25:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadLogFileList() {
|
|
|
|
|
this.fetchLogFiles().then(logFiles => {
|
|
|
|
|
this.logFiles = logFiles;
|
|
|
|
|
this.renderLogList();
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
console.error('获取日志文件列表失败:', error);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fetchLogFiles() {
|
|
|
|
|
try {
|
|
|
|
|
// 直接使用固定的日志目录路径
|
|
|
|
|
const logDirPath = this.logDir;
|
|
|
|
|
|
|
|
|
|
// 读取目录下的所有文件
|
|
|
|
|
const files = await this.readLogDirectory(logDirPath);
|
|
|
|
|
|
|
|
|
|
// 过滤出.log文件并获取文件信息
|
|
|
|
|
const logFiles = [];
|
|
|
|
|
for (const fileName of files) {
|
|
|
|
|
if (fileName.endsWith('.log')) {
|
|
|
|
|
const filePath = `${logDirPath}/${fileName}`;
|
|
|
|
|
const fileStats = await this.getFileStats(filePath);
|
|
|
|
|
|
|
|
|
|
// 格式化修改时间
|
|
|
|
|
const modTime = new Date(fileStats.mtime);
|
|
|
|
|
const formattedTime = this.formatDateTime(modTime);
|
|
|
|
|
|
|
|
|
|
logFiles.push({
|
|
|
|
|
name: fileName,
|
|
|
|
|
time: formattedTime,
|
|
|
|
|
path: filePath
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 按修改时间排序,最新的在前面
|
|
|
|
|
return logFiles.sort((a, b) => {
|
|
|
|
|
return new Date(b.time) - new Date(a.time);
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取日志文件列表失败:', error);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 读取目录内容
|
|
|
|
|
async readLogDirectory(dirPath) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
fetch(`/api/filesystem/readdir?path=${encodeURIComponent(dirPath)}`)
|
|
|
|
|
.then(response => {
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error(`读取目录失败: ${response.status}`);
|
|
|
|
|
}
|
|
|
|
|
return response.json();
|
|
|
|
|
})
|
2025-05-28 16:20:01 +08:00
|
|
|
|
.then(data => {
|
|
|
|
|
resolve(data.files || []);
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('读取目录请求失败:', error);
|
|
|
|
|
reject(error);
|
|
|
|
|
});
|
2025-04-28 12:25:20 +08:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取文件状态信息
|
|
|
|
|
async getFileStats(filePath) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
fetch(`/api/filesystem/stat?path=${encodeURIComponent(filePath)}`)
|
|
|
|
|
.then(response => {
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error(`获取文件信息失败: ${response.status}`);
|
|
|
|
|
}
|
|
|
|
|
return response.json();
|
|
|
|
|
})
|
|
|
|
|
.then(data => resolve(data))
|
|
|
|
|
.catch(error => reject(error));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 格式化日期时间
|
|
|
|
|
formatDateTime(date) {
|
|
|
|
|
const year = date.getFullYear();
|
|
|
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
|
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
|
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
|
|
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
|
|
|
|
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderLogList() {
|
|
|
|
|
const logListElement = this.shadowRoot.getElementById('logList');
|
|
|
|
|
|
|
|
|
|
if (this.logFiles.length === 0) {
|
|
|
|
|
logListElement.innerHTML = '<div class="no-logs">暂无日志记录</div>';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-07 14:49:02 +08:00
|
|
|
|
// 计算总页数
|
|
|
|
|
this.totalPages = Math.ceil(this.logFiles.length / this.pageSize);
|
|
|
|
|
|
|
|
|
|
// 获取当前页的日志文件
|
|
|
|
|
const start = (this.currentPage - 1) * this.pageSize;
|
|
|
|
|
const end = start + this.pageSize;
|
|
|
|
|
const currentPageFiles = this.logFiles.slice(start, end);
|
|
|
|
|
|
|
|
|
|
// 清空列表
|
2025-04-28 12:25:20 +08:00
|
|
|
|
logListElement.innerHTML = '';
|
|
|
|
|
|
2025-05-07 14:49:02 +08:00
|
|
|
|
// 渲染当前页的日志文件
|
|
|
|
|
currentPageFiles.forEach(logFile => {
|
2025-04-28 12:25:20 +08:00
|
|
|
|
const logItemElement = document.createElement('div');
|
|
|
|
|
logItemElement.className = 'log-item';
|
|
|
|
|
logItemElement.dataset.path = logFile.path;
|
|
|
|
|
|
|
|
|
|
const isExpanded = this.expandedLogs.has(logFile.path);
|
|
|
|
|
if (isExpanded) {
|
|
|
|
|
logItemElement.classList.add('expanded');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const iconSrc = isExpanded ? 'assets/icons/png/chevron-up_b.png' : 'assets/icons/png/chevron-down_b.png';
|
|
|
|
|
const iconAlt = isExpanded ? '收起' : '展开';
|
|
|
|
|
|
|
|
|
|
logItemElement.innerHTML = `
|
|
|
|
|
<div class="log-item-header">
|
|
|
|
|
<div class="log-item-info">
|
|
|
|
|
<div class="log-item-title">${logFile.name}</div>
|
|
|
|
|
<div class="log-item-time">${logFile.time}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="log-item-toggle">
|
|
|
|
|
<img src="${iconSrc}" alt="${iconAlt}">
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="log-item-content"></div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const toggleBtn = logItemElement.querySelector('.log-item-toggle');
|
|
|
|
|
const toggleImg = toggleBtn.querySelector('img');
|
|
|
|
|
const content = logItemElement.querySelector('.log-item-content');
|
|
|
|
|
|
|
|
|
|
toggleBtn.addEventListener('click', () => {
|
|
|
|
|
const isExpanded = logItemElement.classList.contains('expanded');
|
|
|
|
|
|
|
|
|
|
if (isExpanded) {
|
|
|
|
|
logItemElement.classList.remove('expanded');
|
|
|
|
|
this.expandedLogs.delete(logFile.path);
|
2025-05-07 14:49:02 +08:00
|
|
|
|
content.innerHTML = '';
|
2025-04-28 12:25:20 +08:00
|
|
|
|
toggleImg.src = 'assets/icons/png/chevron-down_b.png';
|
|
|
|
|
toggleImg.alt = '展开';
|
|
|
|
|
} else {
|
|
|
|
|
logItemElement.classList.add('expanded');
|
|
|
|
|
this.expandedLogs.add(logFile.path);
|
|
|
|
|
this.loadLogContent(logFile.path, content);
|
|
|
|
|
toggleImg.src = 'assets/icons/png/chevron-up_b.png';
|
|
|
|
|
toggleImg.alt = '收起';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (isExpanded) {
|
|
|
|
|
this.loadLogContent(logFile.path, content);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logListElement.appendChild(logItemElement);
|
|
|
|
|
});
|
2025-05-07 14:49:02 +08:00
|
|
|
|
|
|
|
|
|
// 添加分页控制器和刷新按钮
|
|
|
|
|
const paginationHtml = `
|
|
|
|
|
<div class="pagination">
|
|
|
|
|
<button class="pagination-button" id="prevPage" ${this.currentPage === 1 ? 'disabled' : ''}>
|
|
|
|
|
上一页
|
|
|
|
|
</button>
|
|
|
|
|
<span class="pagination-info">
|
|
|
|
|
第 ${this.currentPage} 页 / 共 ${this.totalPages} 页
|
|
|
|
|
</span>
|
|
|
|
|
<button class="pagination-button" id="nextPage" ${this.currentPage === this.totalPages ? 'disabled' : ''}>
|
|
|
|
|
下一页
|
|
|
|
|
</button>
|
|
|
|
|
<div class="pagination-actions">
|
|
|
|
|
<button class="log-action-button" id="refreshLog" title="刷新">
|
|
|
|
|
<img src="assets/icons/png/refresh_b.png" alt="刷新">
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
logListElement.insertAdjacentHTML('beforeend', paginationHtml);
|
|
|
|
|
|
|
|
|
|
// 添加分页按钮事件监听
|
|
|
|
|
const prevButton = logListElement.querySelector('#prevPage');
|
|
|
|
|
const nextButton = logListElement.querySelector('#nextPage');
|
|
|
|
|
const refreshButton = logListElement.querySelector('#refreshLog');
|
|
|
|
|
|
|
|
|
|
prevButton.addEventListener('click', () => {
|
|
|
|
|
if (this.currentPage > 1) {
|
|
|
|
|
this.currentPage--;
|
|
|
|
|
this.renderLogList();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
nextButton.addEventListener('click', () => {
|
|
|
|
|
if (this.currentPage < this.totalPages) {
|
|
|
|
|
this.currentPage++;
|
|
|
|
|
this.renderLogList();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
refreshButton.addEventListener('click', () => {
|
|
|
|
|
this.loadLogFileList();
|
|
|
|
|
});
|
2025-04-28 12:25:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadLogContent(logPath, contentElement) {
|
|
|
|
|
contentElement.innerHTML = '<div style="padding: 8px;">加载中...</div>';
|
|
|
|
|
|
|
|
|
|
this.fetchLogContent(logPath).then(content => {
|
|
|
|
|
let html = '<div class="log-filters">';
|
|
|
|
|
html += '<div class="log-filter active" data-level="all">全部</div>';
|
|
|
|
|
html += '<div class="log-filter" data-level="info">信息</div>';
|
|
|
|
|
html += '<div class="log-filter" data-level="warning">警告</div>';
|
|
|
|
|
html += '<div class="log-filter" data-level="error">错误</div>';
|
|
|
|
|
html += '</div><div class="log-content-messages">';
|
|
|
|
|
|
|
|
|
|
if (!content || content.length === 0) {
|
|
|
|
|
html += '<div style="padding: 8px;">日志为空</div>';
|
|
|
|
|
} else {
|
|
|
|
|
content.forEach(line => {
|
|
|
|
|
let className = 'log-message';
|
|
|
|
|
|
|
|
|
|
if (line.includes('[INFO]')) {
|
|
|
|
|
className += ' info';
|
|
|
|
|
} else if (line.includes('[WARNING]')) {
|
|
|
|
|
className += ' warning';
|
|
|
|
|
} else if (line.includes('[ERROR]')) {
|
|
|
|
|
className += ' error';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
html += `<div class="${className}">${line}</div>`;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
html += '</div>';
|
|
|
|
|
contentElement.innerHTML = html;
|
|
|
|
|
|
|
|
|
|
// 设置过滤器事件
|
|
|
|
|
const filterElements = contentElement.querySelectorAll('.log-filter');
|
|
|
|
|
filterElements.forEach(filter => {
|
|
|
|
|
filter.addEventListener('click', () => {
|
|
|
|
|
filterElements.forEach(f => f.classList.remove('active'));
|
|
|
|
|
filter.classList.add('active');
|
|
|
|
|
|
|
|
|
|
const level = filter.getAttribute('data-level');
|
|
|
|
|
const messagesContainer = contentElement.querySelector('.log-content-messages');
|
|
|
|
|
this.filterLogContent(messagesContainer, level);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
contentElement.innerHTML = `<div class="log-message error" style="padding: 8px;">加载日志内容失败: ${error.message}</div>`;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fetchLogContent(logPath) {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`/api/filesystem/readFile?path=${encodeURIComponent(logPath)}`);
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error(`读取文件失败: ${response.status}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fileContent = await response.text();
|
|
|
|
|
|
|
|
|
|
// 解析日志内容,按行分割
|
|
|
|
|
const lines = fileContent.split('\n')
|
|
|
|
|
.filter(line => line.trim() !== '') // 过滤空行
|
|
|
|
|
.slice(-500); // 只显示最后500行,避免日志过大
|
|
|
|
|
|
|
|
|
|
return lines;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取日志内容失败:', error);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
filterLogContent(contentElement, level) {
|
|
|
|
|
const logMessages = contentElement.querySelectorAll('.log-message');
|
|
|
|
|
|
|
|
|
|
logMessages.forEach(message => {
|
|
|
|
|
if (level === 'all') {
|
|
|
|
|
message.style.display = 'block';
|
|
|
|
|
} else {
|
|
|
|
|
message.style.display = message.classList.contains(level) ? 'block' : 'none';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
customElements.define('run-log', RunLog);
|