697 lines
31 KiB
HTML
697 lines
31 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>X-Request 日志管理</title>
|
||
<!-- 引入Tailwind CSS (离线版本) -->
|
||
<script src="vendor/tailwind.min.js"></script>
|
||
<!-- 引入内联 SVG 图标系统 (完全离线) -->
|
||
<script src="vendor/icons.js"></script>
|
||
<style>
|
||
.inline-icon, .inline-emoji {
|
||
display: inline-block;
|
||
vertical-align: middle;
|
||
}
|
||
.inline-icon svg {
|
||
width: 1em;
|
||
height: 1em;
|
||
fill: currentColor;
|
||
}
|
||
.inline-emoji {
|
||
font-size: 1em;
|
||
line-height: 1;
|
||
}
|
||
@keyframes spin {
|
||
from { transform: rotate(0deg); }
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
.inline-icon[data-spin="true"], .inline-emoji[data-spin="true"] {
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
</style>
|
||
<!-- 配置Tailwind -->
|
||
<script>
|
||
tailwind.config = {
|
||
theme: {
|
||
extend: {
|
||
colors: {
|
||
primary: '#3b82f6',
|
||
secondary: '#8b5cf6',
|
||
success: '#10b981',
|
||
warning: '#f59e0b',
|
||
danger: '#ef4444',
|
||
},
|
||
},
|
||
}
|
||
}
|
||
</script>
|
||
<style type="text/tailwindcss">
|
||
@layer utilities {
|
||
.content-auto {
|
||
content-visibility: auto;
|
||
}
|
||
.scrollbar-hide {
|
||
-ms-overflow-style: none;
|
||
scrollbar-width: none;
|
||
}
|
||
.scrollbar-hide::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
/* 全屏样式 */
|
||
html, body {
|
||
height: 100vh;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
main {
|
||
flex: 1;
|
||
padding: 1rem;
|
||
}
|
||
|
||
.max-w-7xl {
|
||
max-width: 100%;
|
||
}
|
||
|
||
/* 调整网格布局高度 */
|
||
.h-full {
|
||
height: 100%;
|
||
}
|
||
|
||
/* 调整滚动区域最大高度 */
|
||
.max-h-screen-content {
|
||
max-height: calc(100vh - 200px);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="bg-gray-50 min-h-screen">
|
||
<!-- 顶部导航栏 -->
|
||
<nav class="bg-white shadow-md h-16">
|
||
<div class="w-full mx-auto px-4 sm:px-6 lg:px-8">
|
||
<div class="flex justify-between h-16 items-center">
|
||
<div class="flex items-center">
|
||
<div class="flex-shrink-0 flex items-center">
|
||
<span class="text-3xl font-bold text-primary">X</span>
|
||
<span class="ml-2 text-xl font-semibold text-gray-800">X-Request 管理系统</span>
|
||
</div>
|
||
<div class="ml-6 flex items-center space-x-4">
|
||
<a href="/" class="px-3 py-2 rounded-md text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/10 hover:border-b-2 hover:border-primary transition-colors">
|
||
<i class="fa fa-home mr-1"></i> 首页
|
||
</a>
|
||
<a href="/log.html" class="px-3 py-2 rounded-md text-sm font-medium text-primary bg-primary/10 border-b-2 border-primary">
|
||
<i class="fa fa-file-text mr-1"></i> 日志管理
|
||
</a>
|
||
<a href="/doc.html" class="px-3 py-2 rounded-md text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/10 hover:border-b-2 hover:border-primary transition-colors">
|
||
<i class="fa fa-book mr-1"></i> 接口文档
|
||
</a>
|
||
<a href="/status.html" class="px-3 py-2 rounded-md text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/10 hover:border-b-2 hover:border-primary transition-colors">
|
||
<i class="fa fa-heartbeat mr-1"></i> 系统状态
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center">
|
||
<div class="mr-4">
|
||
<span class="text-sm text-gray-500">更新时间: </span>
|
||
<span id="last-updated" class="font-medium text-gray-800">--:--:--</span>
|
||
</div>
|
||
<button type="button" id="refresh-btn" class="bg-primary hover:bg-primary/90 text-white px-3 py-1.5 rounded-md text-sm font-medium transition-colors mr-2">
|
||
<i class="fa fa-refresh mr-1"></i> 刷新
|
||
</button>
|
||
<button type="button" id="batch-download-btn" onclick="batchDownload()" class="bg-success hover:bg-success/90 text-white px-3 py-1.5 rounded-md text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed mr-2" disabled>
|
||
<i class="fa fa-download mr-1"></i> 批量下载
|
||
<span id="selected-count-download" class="ml-1 bg-white/20 px-2 py-0.5 rounded-full text-xs">0</span>
|
||
</button>
|
||
<button type="button" id="batch-delete-btn" onclick="batchDelete()" class="bg-danger hover:bg-danger/90 text-white px-3 py-1.5 rounded-md text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed" disabled>
|
||
<i class="fa fa-trash mr-1"></i> 批量删除
|
||
<span id="selected-count-delete" class="ml-1 bg-white/20 px-2 py-0.5 rounded-full text-xs">0</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- 主要内容 -->
|
||
<main class="w-full px-4 sm:px-6 lg:px-8 py-4">
|
||
<!-- 日志管理视图 -->
|
||
<div class="bg-white rounded-lg shadow-md p-4 h-full">
|
||
<h2 class="text-xl font-bold text-gray-800 mb-4">日志管理</h2>
|
||
|
||
<!-- 日志分类和内容 -->
|
||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6 h-screen-content">
|
||
<!-- 左侧:日期列表 -->
|
||
<div class="lg:col-span-2">
|
||
<div class="bg-white rounded-lg shadow-sm p-4 h-full flex flex-col">
|
||
<h3 class="text-lg font-semibold text-gray-800 mb-4">按日期分类</h3>
|
||
<div class="space-y-2 flex-1 flex flex-col">
|
||
<div class="flex items-center">
|
||
<input type="checkbox" id="select-all-dates" class="rounded text-primary focus:ring-primary h-4 w-4 mr-2">
|
||
<label for="select-all-dates" class="text-sm font-medium text-gray-700">全选日期</label>
|
||
</div>
|
||
<div id="dates-list" class="space-y-1 flex-1 overflow-y-auto mt-2">
|
||
<!-- 日期列表将通过JavaScript动态生成 -->
|
||
<div class="text-center text-gray-500 py-8">
|
||
<i class="fa fa-spinner fa-spin text-xl mb-2"></i>
|
||
<p>加载中...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 中间:日志文件列表 -->
|
||
<div class="lg:col-span-3">
|
||
<div class="bg-white rounded-lg shadow-sm p-4 h-full flex flex-col">
|
||
<div class="flex justify-between items-center mb-4">
|
||
<h3 id="selected-date-title" class="text-lg font-semibold text-gray-800">选择日期查看日志</h3>
|
||
<div class="flex items-center">
|
||
<input type="checkbox" id="select-all-logs" class="rounded text-primary focus:ring-primary h-4 w-4 mr-2">
|
||
<label for="select-all-logs" class="text-sm font-medium text-gray-700">全选日志</label>
|
||
</div>
|
||
</div>
|
||
<div id="logs-list" class="space-y-2 flex-1 overflow-y-auto">
|
||
<!-- 日志文件列表将通过JavaScript动态生成 -->
|
||
<div class="text-center text-gray-500 py-8">
|
||
<p>请选择左侧日期</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:日志内容 -->
|
||
<div class="lg:col-span-7">
|
||
<div class="bg-white rounded-lg shadow-sm p-4 h-full flex flex-col">
|
||
<div class="flex justify-between items-center mb-4">
|
||
<h3 id="current-log-title" class="text-lg font-semibold text-gray-800">日志内容</h3>
|
||
<div class="flex items-center space-x-2">
|
||
<select id="log-mode" class="border border-gray-300 rounded-md text-sm px-2 py-1">
|
||
<option value="latest" selected>最新的N条日志(顶部)</option>
|
||
<option value="oldest">最早的N条日志(顶部)</option>
|
||
</select>
|
||
<select id="log-lines" class="border border-gray-300 rounded-md text-sm px-2 py-1">
|
||
<option value="50">50行</option>
|
||
<option value="100" selected>100行</option>
|
||
<option value="200">200行</option>
|
||
<option value="500">500行</option>
|
||
<option value="1000">1000行</option>
|
||
<option value="2000">2000行</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="bg-gray-900 text-gray-200 rounded-md p-4 flex-1 overflow-auto">
|
||
<pre id="log-content" class="text-sm whitespace-pre-wrap">请选择日志文件查看内容...</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- 自定义确认对话框 -->
|
||
<div id="confirm-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
|
||
<div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4">
|
||
<div class="p-6">
|
||
<h3 class="text-lg font-semibold text-gray-800 mb-2" id="confirm-title">确认操作</h3>
|
||
<p class="text-gray-600 mb-6" id="confirm-message">确定要执行此操作吗?</p>
|
||
<div class="flex justify-end space-x-3">
|
||
<button type="button" id="confirm-cancel" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors">
|
||
取消
|
||
</button>
|
||
<button type="button" id="confirm-ok" class="px-4 py-2 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors">
|
||
确定
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- JavaScript -->
|
||
<script>
|
||
// 全局变量
|
||
let selectedDate = null;
|
||
let currentLogFile = null;
|
||
let selectedFiles = new Set(); // 存储选中的日志文件路径
|
||
let dates = []; // 存储所有日期
|
||
|
||
// 自定义确认对话框
|
||
let confirmResolver = null;
|
||
function showConfirm(title, message) {
|
||
return new Promise((resolve) => {
|
||
confirmResolver = resolve;
|
||
const modal = document.getElementById('confirm-modal');
|
||
const titleEl = document.getElementById('confirm-title');
|
||
const messageEl = document.getElementById('confirm-message');
|
||
|
||
titleEl.textContent = title;
|
||
messageEl.textContent = message;
|
||
modal.classList.remove('hidden');
|
||
modal.classList.add('flex');
|
||
});
|
||
}
|
||
|
||
// 确认对话框事件监听
|
||
document.getElementById('confirm-cancel').addEventListener('click', () => {
|
||
const modal = document.getElementById('confirm-modal');
|
||
modal.classList.add('hidden');
|
||
modal.classList.remove('flex');
|
||
if (confirmResolver) {
|
||
confirmResolver(false);
|
||
confirmResolver = null;
|
||
}
|
||
});
|
||
|
||
document.getElementById('confirm-ok').addEventListener('click', () => {
|
||
const modal = document.getElementById('confirm-modal');
|
||
modal.classList.add('hidden');
|
||
modal.classList.remove('flex');
|
||
if (confirmResolver) {
|
||
confirmResolver(true);
|
||
confirmResolver = null;
|
||
}
|
||
});
|
||
|
||
// 工具函数
|
||
function formatBytes(bytes, decimals = 2) {
|
||
if (bytes === 0) return '0 Bytes';
|
||
const k = 1024;
|
||
const dm = decimals < 0 ? 0 : decimals;
|
||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||
}
|
||
|
||
// 获取日期列表
|
||
async function fetchDates() {
|
||
try {
|
||
const response = await fetch('/monitoring/logs');
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
dates = data.data.dates || [];
|
||
renderDatesList();
|
||
return true;
|
||
}
|
||
} catch (error) {
|
||
console.error('获取日期列表失败:', error);
|
||
document.getElementById('dates-list').innerHTML = '<div class="text-center text-red-500 py-8"><i class="fa fa-exclamation-circle text-xl mb-2"></i><p>获取日期列表失败</p></div>';
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 渲染日期列表
|
||
function renderDatesList() {
|
||
const datesList = document.getElementById('dates-list');
|
||
|
||
if (dates.length === 0) {
|
||
datesList.innerHTML = '<div class="text-center text-gray-500 py-8"><p>没有找到日志日期</p></div>';
|
||
return;
|
||
}
|
||
|
||
let html = '';
|
||
for (const date of dates) {
|
||
const isSelected = selectedDate === date.date;
|
||
html += `
|
||
<div class="flex items-center p-2 rounded-md border border-gray-200 cursor-pointer hover:bg-gray-100 transition-colors ${isSelected ? 'bg-primary/10 border-l-4 border-primary' : ''}">
|
||
<input type="checkbox" data-date="${date.date}" class="date-checkbox rounded text-primary focus:ring-primary h-4 w-4 mr-2">
|
||
<div class="flex-1" onclick="selectDate('${date.date}')">
|
||
<div class="font-medium text-gray-800">${date.date}</div>
|
||
<div class="text-xs text-gray-500">${date.log_count} 个日志文件</div>
|
||
</div>
|
||
<div class="text-xs text-gray-500">${new Date(date.last_modified * 1000).toLocaleString()}</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
datesList.innerHTML = html;
|
||
|
||
// 添加日期复选框事件监听
|
||
document.querySelectorAll('.date-checkbox').forEach(checkbox => {
|
||
checkbox.addEventListener('change', handleDateCheckboxChange);
|
||
});
|
||
}
|
||
|
||
// 选择日期
|
||
async function selectDate(date) {
|
||
selectedDate = date;
|
||
currentLogFile = null;
|
||
selectedFiles.clear();
|
||
updateSelectedCount();
|
||
|
||
// 更新日期列表样式
|
||
renderDatesList();
|
||
|
||
// 更新日志列表
|
||
await fetchLogsByDate(date);
|
||
|
||
// 清空日志内容
|
||
document.getElementById('current-log-title').textContent = '日志内容';
|
||
document.getElementById('log-content').textContent = '请选择日志文件查看内容...';
|
||
}
|
||
|
||
// 获取指定日期的日志文件
|
||
async function fetchLogsByDate(date) {
|
||
try {
|
||
const response = await fetch(`/monitoring/logs?date=${date}`);
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
const logs = data.data.logs || [];
|
||
renderLogsList(logs, date);
|
||
return true;
|
||
}
|
||
} catch (error) {
|
||
console.error('获取日志文件列表失败:', error);
|
||
document.getElementById('logs-list').innerHTML = '<div class="text-center text-red-500 py-8"><i class="fa fa-exclamation-circle text-xl mb-2"></i><p>获取日志文件列表失败</p></div>';
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 渲染日志文件列表
|
||
function renderLogsList(logs, date) {
|
||
const logsList = document.getElementById('logs-list');
|
||
const selectedDateTitle = document.getElementById('selected-date-title');
|
||
|
||
selectedDateTitle.textContent = `${date} 日志文件 (${logs.length} 个)`;
|
||
|
||
if (logs.length === 0) {
|
||
logsList.innerHTML = '<div class="text-center text-gray-500 py-8"><p>该日期没有日志文件</p></div>';
|
||
return;
|
||
}
|
||
|
||
let html = '';
|
||
for (const log of logs) {
|
||
const isSelected = selectedFiles.has(log.relative_path);
|
||
// 将Windows风格的路径分隔符替换为Unix风格
|
||
const displayPath = log.relative_path.replace(/\\/g, '/');
|
||
html += `
|
||
<div class="flex items-center p-2 rounded-md border border-gray-200 hover:bg-gray-100 transition-colors">
|
||
<input type="checkbox" data-path="${log.relative_path}" class="log-checkbox rounded text-primary focus:ring-primary h-4 w-4 mr-2" ${isSelected ? 'checked' : ''}>
|
||
<div class="flex-1" onclick="viewLog('${displayPath}', '${log.name}')">
|
||
<div class="font-medium text-gray-800">${log.name}</div>
|
||
<div class="text-xs text-gray-500">${formatBytes(log.size)} · ${new Date(log.modified_at * 1000).toLocaleString()}</div>
|
||
</div>
|
||
<a href="/monitoring/logs/${log.relative_path}/download" target="_blank" class="text-primary hover:text-primary/80 mr-2" title="下载">
|
||
<i class="fa fa-download"></i>
|
||
</a>
|
||
<button type="button" onclick="deleteLog('${displayPath}', '${log.name}')" class="text-danger hover:text-danger/80" title="删除">
|
||
<i class="fa fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
logsList.innerHTML = html;
|
||
|
||
// 添加日志复选框事件监听
|
||
document.querySelectorAll('.log-checkbox').forEach(checkbox => {
|
||
checkbox.addEventListener('change', handleLogCheckboxChange);
|
||
});
|
||
}
|
||
|
||
// 查看日志内容
|
||
async function viewLog(filePath, fileName) {
|
||
currentLogFile = filePath;
|
||
|
||
// 更新标题
|
||
document.getElementById('current-log-title').textContent = `日志内容 - ${fileName}`;
|
||
|
||
try {
|
||
const entries = document.getElementById('log-lines').value;
|
||
const mode = document.getElementById('log-mode').value;
|
||
// 将Windows风格的路径分隔符替换为Unix风格
|
||
const normalizedPath = filePath.replace(/\\/g, '/');
|
||
const parts = normalizedPath.split('/');
|
||
const date = parts[0];
|
||
const logName = parts.slice(1).join('/');
|
||
|
||
const response = await fetch(`/monitoring/logs/${date}/${logName}?entries=${entries}&mode=${mode}`);
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
document.getElementById('log-content').textContent = data.data.content;
|
||
// 根据显示模式滚动
|
||
const logContent = document.getElementById('log-content');
|
||
logContent.scrollTop = 0;
|
||
} else {
|
||
document.getElementById('log-content').textContent = `获取日志内容失败: ${data.message}`;
|
||
}
|
||
} catch (error) {
|
||
console.error('获取日志内容失败:', error);
|
||
document.getElementById('log-content').textContent = `获取日志内容失败: ${error.message}`;
|
||
}
|
||
}
|
||
|
||
// 处理日期复选框变化
|
||
async function handleDateCheckboxChange(e) {
|
||
const date = e.target.dataset.date;
|
||
const isChecked = e.target.checked;
|
||
|
||
if (isChecked) {
|
||
// 选中该日期下的所有日志文件
|
||
await fetchLogsByDate(date);
|
||
|
||
// 勾选该日期下的所有日志文件
|
||
document.querySelectorAll('.log-checkbox').forEach(checkbox => {
|
||
const path = checkbox.dataset.path;
|
||
if (path && path.startsWith(date)) {
|
||
checkbox.checked = true;
|
||
selectedFiles.add(path);
|
||
}
|
||
});
|
||
} else {
|
||
// 取消选中该日期下的所有日志文件
|
||
document.querySelectorAll('.log-checkbox').forEach(checkbox => {
|
||
const path = checkbox.dataset.path;
|
||
if (path && path.startsWith(date)) {
|
||
checkbox.checked = false;
|
||
selectedFiles.delete(path);
|
||
}
|
||
});
|
||
}
|
||
|
||
updateSelectedCount();
|
||
}
|
||
|
||
// 处理日志复选框变化
|
||
function handleLogCheckboxChange(e) {
|
||
const filePath = e.target.dataset.path;
|
||
const isChecked = e.target.checked;
|
||
|
||
if (isChecked) {
|
||
selectedFiles.add(filePath);
|
||
} else {
|
||
selectedFiles.delete(filePath);
|
||
}
|
||
|
||
updateSelectedCount();
|
||
}
|
||
|
||
// 更新选中计数
|
||
function updateSelectedCount() {
|
||
const count = selectedFiles.size;
|
||
// 更新批量下载的计数
|
||
document.getElementById('selected-count-download').textContent = count;
|
||
// 更新批量删除的计数
|
||
document.getElementById('selected-count-delete').textContent = count;
|
||
// 更新按钮状态
|
||
document.getElementById('batch-download-btn').disabled = count === 0;
|
||
document.getElementById('batch-delete-btn').disabled = count === 0;
|
||
}
|
||
|
||
// 删除单个日志文件
|
||
async function deleteLog(filePath, fileName) {
|
||
const confirmed = await showConfirm('删除确认', `确定要删除日志文件 "${fileName}" 吗?此操作不可恢复。`);
|
||
if (!confirmed) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(`/monitoring/logs/${filePath}`, {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
// 更新日志列表
|
||
await fetchLogsByDate(selectedDate);
|
||
|
||
// 清空日志内容如果当前显示的是被删除的日志
|
||
if (currentLogFile === filePath) {
|
||
document.getElementById('current-log-title').textContent = '日志内容';
|
||
document.getElementById('log-content').textContent = '请选择日志文件查看内容...';
|
||
currentLogFile = null;
|
||
}
|
||
|
||
// 更新选中文件集合
|
||
selectedFiles.delete(filePath);
|
||
updateSelectedCount();
|
||
|
||
// 显示成功提示
|
||
alert(`日志文件 "${fileName}" 删除成功`);
|
||
} else {
|
||
alert(`删除失败: ${data.message}`);
|
||
}
|
||
} catch (error) {
|
||
console.error('删除日志失败:', error);
|
||
alert(`删除失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
// 批量删除日志文件
|
||
async function batchDelete() {
|
||
if (selectedFiles.size === 0) {
|
||
alert('请先选择日志文件');
|
||
return;
|
||
}
|
||
|
||
const confirmed = await showConfirm('批量删除确认', `确定要删除选中的 ${selectedFiles.size} 个日志文件吗?此操作不可恢复。`);
|
||
if (!confirmed) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/monitoring/logs/batch/delete', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
files: Array.from(selectedFiles)
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
// 更新日志列表
|
||
await fetchLogsByDate(selectedDate);
|
||
|
||
// 清空当前日志内容
|
||
document.getElementById('current-log-title').textContent = '日志内容';
|
||
document.getElementById('log-content').textContent = '请选择日志文件查看内容...';
|
||
currentLogFile = null;
|
||
|
||
// 清空选中文件集合
|
||
selectedFiles.clear();
|
||
updateSelectedCount();
|
||
|
||
// 显示成功提示
|
||
alert(`批量删除完成。成功删除 ${data.data.deleted} 个文件,失败 ${data.data.failed} 个文件。`);
|
||
} else {
|
||
alert(`批量删除失败: ${data.message}`);
|
||
}
|
||
} catch (error) {
|
||
console.error('批量删除失败:', error);
|
||
alert(`批量删除失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
// 全选/取消全选日期
|
||
function toggleSelectAllDates() {
|
||
const selectAllCheckbox = document.getElementById('select-all-dates');
|
||
const isChecked = selectAllCheckbox.checked;
|
||
|
||
document.querySelectorAll('.date-checkbox').forEach(checkbox => {
|
||
checkbox.checked = isChecked;
|
||
// 触发change事件,调用handleDateCheckboxChange处理日志文件联动
|
||
checkbox.dispatchEvent(new Event('change'));
|
||
});
|
||
}
|
||
|
||
// 全选/取消全选日志
|
||
function toggleSelectAllLogs() {
|
||
const selectAllCheckbox = document.getElementById('select-all-logs');
|
||
const isChecked = selectAllCheckbox.checked;
|
||
|
||
document.querySelectorAll('.log-checkbox').forEach(checkbox => {
|
||
checkbox.checked = isChecked;
|
||
checkbox.dispatchEvent(new Event('change'));
|
||
});
|
||
}
|
||
|
||
// 批量下载日志
|
||
async function batchDownload() {
|
||
if (selectedFiles.size === 0) {
|
||
alert('请先选择日志文件');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/monitoring/logs/download', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
files: Array.from(selectedFiles)
|
||
})
|
||
});
|
||
|
||
if (response.ok) {
|
||
// 创建下载链接
|
||
const blob = await response.blob();
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `logs_${new Date().toISOString().slice(0, 10)}_batch.zip`;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
window.URL.revokeObjectURL(url);
|
||
} else {
|
||
const data = await response.json();
|
||
alert(`批量下载失败: ${data.message}`);
|
||
}
|
||
} catch (error) {
|
||
console.error('批量下载失败:', error);
|
||
alert(`批量下载失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
// 刷新所有数据
|
||
async function refreshAll() {
|
||
await fetchDates();
|
||
if (selectedDate) {
|
||
await fetchLogsByDate(selectedDate);
|
||
}
|
||
|
||
// 更新最后更新时间
|
||
const now = new Date();
|
||
document.getElementById('last-updated').textContent = now.toLocaleTimeString();
|
||
}
|
||
|
||
// 初始化
|
||
async function init() {
|
||
// 初始加载数据
|
||
await refreshAll();
|
||
|
||
// 事件监听
|
||
document.getElementById('refresh-btn').addEventListener('click', refreshAll);
|
||
document.getElementById('select-all-dates').addEventListener('change', toggleSelectAllDates);
|
||
document.getElementById('select-all-logs').addEventListener('change', toggleSelectAllLogs);
|
||
|
||
// 日志行数或显示模式变化事件
|
||
document.getElementById('log-lines').addEventListener('change', () => {
|
||
if (currentLogFile) {
|
||
// 直接调用viewLog,使用当前的filePath和fileName
|
||
viewLog(currentLogFile, document.getElementById('current-log-title').textContent.replace('日志内容 - ', ''));
|
||
}
|
||
});
|
||
|
||
// 显示模式变化事件
|
||
document.getElementById('log-mode').addEventListener('change', () => {
|
||
if (currentLogFile) {
|
||
// 直接调用viewLog,使用当前的filePath和fileName
|
||
viewLog(currentLogFile, document.getElementById('current-log-title').textContent.replace('日志内容 - ', ''));
|
||
}
|
||
});
|
||
}
|
||
|
||
// 页面加载完成后初始化
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
</script>
|
||
</body>
|
||
</html> |