392 lines
12 KiB
HTML
392 lines
12 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Log File Browser</title>
|
||
<script src="resources/plugins/jquery.1.12.4.min.js"></script>
|
||
<script src="resources/plugins/bootstrap-3.3.0/js/bootstrap.min.js"></script>
|
||
<script src="resources/plugins/waves-0.7.5/waves.min.js"></script>
|
||
<script src="resources/plugins/jquery.cookie.js"></script>
|
||
<script src="resources/js/notify.js"></script>
|
||
<script src="resources/js/common.js"></script>
|
||
<script src="resources/js/lang.js"></script>
|
||
<style>
|
||
* {
|
||
box-sizing: border-box;
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
}
|
||
body {
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
background-color: #f5f7fa;
|
||
color: #333;
|
||
}
|
||
header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 1px solid #e0e6ed;
|
||
}
|
||
h1 {
|
||
margin: 0;
|
||
font-size: 24px;
|
||
color: #2c3e50;
|
||
flex-grow: 1;
|
||
}
|
||
.btn {
|
||
padding: 8px 16px;
|
||
background-color: #3498db;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: background-color 0.2s;
|
||
}
|
||
.btn:hover {
|
||
background-color: #2980b9;
|
||
}
|
||
.btn.back {
|
||
background-color: #7f8c8d;
|
||
}
|
||
.btn.back:hover {
|
||
background-color: #34495e;
|
||
}
|
||
#file-list {
|
||
background: white;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||
overflow: hidden;
|
||
}
|
||
.list-header {
|
||
padding: 12px 20px;
|
||
background-color: #3498db;
|
||
color: white;
|
||
font-weight: bold;
|
||
display: grid;
|
||
grid-template-columns: 1fr 100px;
|
||
}
|
||
.file-item {
|
||
padding: 12px 20px;
|
||
border-bottom: 1px solid #eee;
|
||
display: grid;
|
||
grid-template-columns: 1fr 100px;
|
||
cursor: pointer;
|
||
transition: background-color 0.2s;
|
||
}
|
||
.file-item:hover {
|
||
background-color: #f8f9fa;
|
||
}
|
||
.file-name {
|
||
font-weight: 500;
|
||
color: #2c3e50;
|
||
word-break: break-all;
|
||
}
|
||
.file-size {
|
||
color: #7f8c8d;
|
||
text-align: right;
|
||
font-size: 0.9em;
|
||
}
|
||
#file-content {
|
||
background: white;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||
padding: 20px;
|
||
margin-top: 20px;
|
||
display: none;
|
||
}
|
||
.content-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
#content-title {
|
||
margin: 0;
|
||
font-size: 20px;
|
||
color: #2c3e50;
|
||
flex-grow: 1;
|
||
word-break: break-all;
|
||
}
|
||
#file-text {
|
||
white-space: pre-wrap;
|
||
font-family: monospace;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
background-color: #f8f9fa;
|
||
padding: 15px;
|
||
border-radius: 4px;
|
||
max-height: 60vh;
|
||
overflow: auto;
|
||
}
|
||
.empty-message {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
color: #7f8c8d;
|
||
}
|
||
.loading {
|
||
padding: 20px;
|
||
text-align: center;
|
||
color: #7f8c8d;
|
||
}
|
||
.content-header {
|
||
display: flex;
|
||
gap: 16px; /* 控制按钮间距,按需调整 */
|
||
}
|
||
/* 新增网格布局样式 */
|
||
#list-container {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, 1fr); /* 一行4列 */
|
||
gap: 16px; /* 项目间距 */
|
||
padding: 16px;
|
||
}
|
||
|
||
.file-item {
|
||
background: white;
|
||
border: 1px solid #e0e6ed;
|
||
border-radius: 8px;
|
||
padding: 12px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
overflow: hidden; /* 防止内容溢出 */
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.file-item:hover {
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||
transform: translateY(-2px);
|
||
border-color: #3498db;
|
||
}
|
||
|
||
.file-name {
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 响应式适配 */
|
||
@media (max-width: 1200px) {
|
||
#list-container {
|
||
grid-template-columns: repeat(3, 1fr); /* 中等屏幕显示3列 */
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
#list-container {
|
||
grid-template-columns: repeat(2, 1fr); /* 小屏幕显示2列 */
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
#list-container {
|
||
grid-template-columns: 1fr; /* 超小屏幕显示1列 */
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<h1>Log File Browser</h1>
|
||
<button id="refresh-btn" class="btn">Refresh list</button>
|
||
</header>
|
||
|
||
<div id="file-list">
|
||
<div class="list-header">
|
||
<span>file name</span>
|
||
</div>
|
||
<div id="list-container">
|
||
<div class="loading">Loading file list...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="file-content">
|
||
<div class="content-header">
|
||
<h2 id="content-title"></h2>
|
||
<button id="copy-btn" class="btn">Copy</button>
|
||
<button id="back-btn" class="btn back">Return to list</button>
|
||
</div>
|
||
<pre id="file-text"></pre>
|
||
</div>
|
||
|
||
<script>
|
||
check_permission();
|
||
const listContainer = document.getElementById('list-container');
|
||
const fileContent = document.getElementById('file-content');
|
||
const contentTitle = document.getElementById('content-title');
|
||
const fileText = document.getElementById('file-text');
|
||
const refreshBtn = document.getElementById('refresh-btn');
|
||
const backBtn = document.getElementById('back-btn');
|
||
|
||
// 获取日志文件列表(适配新版后端API)
|
||
async function loglist_get() {
|
||
const payload = {
|
||
password: $.cookie("pwd"),
|
||
cmd: 'getdir',
|
||
path: '/data/log'
|
||
};
|
||
|
||
try {
|
||
const response = await $.ajax({
|
||
url: debugserver + '/api',
|
||
type: 'POST',
|
||
contentType: "application/json",
|
||
dataType: "json",
|
||
data: JSON.stringify(payload)
|
||
});
|
||
|
||
// 转换后端数据结构为前端需要的格式
|
||
return {
|
||
status: response.result ? 'success' : 'error',
|
||
files: response.files.map(filename => ({ name: filename }))
|
||
};
|
||
|
||
} catch (error) {
|
||
console.error("API request failed:", error);
|
||
return {
|
||
status: 'error',
|
||
message: 'Failed to obtain file list'
|
||
};
|
||
}
|
||
}
|
||
|
||
async function renderFileList() {
|
||
listContainer.innerHTML = '<div class="loading">Loading file list...</div>';
|
||
const result = await loglist_get();
|
||
|
||
if (result.status === 'success' && result.files && result.files.length > 0) {
|
||
// 按文件名中的时间戳倒序排序(新文件在前)
|
||
result.files.sort((a, b) => {
|
||
// 提取文件名中的时间戳(格式如:20250731-15-36-55.log)
|
||
const timestampA = a.name.split('.')[0]; // 去掉后缀
|
||
const timestampB = b.name.split('.')[0];
|
||
// 按字符串倒序排列(新文件在前)
|
||
return timestampB.localeCompare(timestampA);
|
||
});
|
||
|
||
let listHTML = '';
|
||
result.files.forEach(file => {
|
||
listHTML += `
|
||
<div class="file-item" data-filename="${file.name}">
|
||
<div class="file-name">${file.name}</div>
|
||
</div>
|
||
`;
|
||
});
|
||
listContainer.innerHTML = listHTML;
|
||
|
||
// 添加点击事件处理(保持不变)
|
||
document.querySelectorAll('.file-item').forEach(item => {
|
||
item.addEventListener('click', async () => {
|
||
const filename = item.dataset.filename;
|
||
await showFileContent(filename);
|
||
});
|
||
});
|
||
} else {
|
||
const errorMsg = result.message || 'Log file not found';
|
||
listContainer.innerHTML = `<div class="empty-message">${errorMsg}</div>`;
|
||
}
|
||
}
|
||
// 获取文件内容(连接真实API)
|
||
async function fetchFileContent(filename) {
|
||
const payload = {
|
||
password: $.cookie("pwd"),
|
||
cmd: 'getfile',
|
||
path: '/data/log/'+filename
|
||
};
|
||
try {
|
||
const response = await $.ajax({
|
||
url: debugserver + '/api',
|
||
type: 'POST',
|
||
contentType: "application/json",
|
||
dataType: "json",
|
||
data: JSON.stringify(payload)
|
||
});
|
||
return {
|
||
status: response.result ? 'success' : 'error',
|
||
file: response.base64
|
||
};
|
||
} catch (error) {
|
||
console.error("API request failed:", error);
|
||
return {
|
||
status: 'error',
|
||
message: 'Failed to getfile'
|
||
};
|
||
}
|
||
}
|
||
async function showFileContent(filename) {
|
||
fileContent.style.display = 'block';
|
||
contentTitle.textContent = filename;
|
||
fileText.textContent = 'loading...';
|
||
const result=await fetchFileContent(filename);
|
||
if (result.status === 'success')
|
||
{
|
||
const base64Data = result.file;
|
||
const binaryString = atob(base64Data);
|
||
const bytes = new Uint8Array(binaryString.length);
|
||
for (let i = 0; i < binaryString.length; i++) {
|
||
bytes[i] = binaryString.charCodeAt(i);
|
||
}
|
||
const decoder = new TextDecoder('utf-8');
|
||
const utf8Text = decoder.decode(bytes);
|
||
fileText.textContent = utf8Text;
|
||
}
|
||
else
|
||
fileText.textContent = `error: ${result.message || 'Unknown error'}`;
|
||
fileContent.scrollIntoView({ behavior: 'smooth' });
|
||
}
|
||
|
||
// 返回文件列表
|
||
function backToList() {
|
||
fileContent.style.display = 'none';
|
||
document.querySelector('header').scrollIntoView({ behavior: 'smooth' });
|
||
}
|
||
|
||
// 事件监听
|
||
refreshBtn.addEventListener('click', renderFileList);
|
||
backBtn.addEventListener('click', backToList);
|
||
|
||
// 初始化
|
||
document.addEventListener('DOMContentLoaded', renderFileList);
|
||
|
||
// 在JS中添加以下代码
|
||
// 获取复制按钮DOM
|
||
const copyBtn = document.getElementById('copy-btn');
|
||
|
||
// 复制功能实现
|
||
async function copyFileContent() {
|
||
const content = fileText.innerText;
|
||
|
||
try {
|
||
// 现代浏览器方案(Clipboard API)
|
||
if (navigator.clipboard) {
|
||
await navigator.clipboard.writeText(content);
|
||
notify('Copied to clipboard!');
|
||
return;
|
||
}
|
||
|
||
// 兼容旧浏览器的回退方案
|
||
const textarea = document.createElement('textarea');
|
||
textarea.value = content;
|
||
textarea.setAttribute('readonly', '');
|
||
textarea.style.position = 'absolute';
|
||
textarea.style.left = '-9999px';
|
||
document.body.appendChild(textarea);
|
||
|
||
// 选择并复制
|
||
textarea.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(textarea);
|
||
notify('Copied!');
|
||
} catch (err) {
|
||
console.error('Copy failed:', err);
|
||
notify('Copy failed! Please copy manually');
|
||
}
|
||
}
|
||
// 事件绑定(在DOMContentLoaded或初始化时添加)
|
||
copyBtn.addEventListener('click', copyFileContent);
|
||
</script>
|
||
</body>
|
||
</html> |