558 lines
20 KiB
JavaScript
Raw Normal View History

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'; // 直接设置日志目录路径
// 添加分页相关的状态变量
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;
}
.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;
}
.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-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();
})
.then(data => resolve(data.files || []))
.catch(error => reject(error));
});
}
// 获取文件状态信息
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;
}
// 计算总页数
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 = '';
// 渲染当前页的日志文件
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);
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);
});
// 添加分页控制器和刷新按钮
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);