refactor(frontend): canonicalize active panel files

- replace rewrite/clean transitional panel files with canonical filenames

- update imports and navigation architecture notes to match runtime truth

- ignore local .codex_tmp workspace artifacts
This commit is contained in:
2026-04-27 08:05:10 +08:00
parent 5290b071b3
commit 5a0623fd9b
18 changed files with 1176 additions and 3105 deletions
+118 -98
View File
@@ -1,6 +1,8 @@
import React, { useState, useEffect, useCallback } from 'react';
import { listLogs, getLogContent, deleteLog } from './api/logs';
const PAGE_SIZE = 1000;
const LogManagementPanel = ({ isAdmin }) => {
const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(false);
@@ -19,17 +21,29 @@ const LogManagementPanel = ({ isAdmin }) => {
setLogs(data);
} catch (error) {
console.error('加载日志列表失败:', error);
alert(`加载日志列表失败: ${error.response?.data?.detail || error.message}`);
alert(`加载日志列表失败${error.response?.data?.detail || error.message}`);
} finally {
setLoading(false);
}
}, [filterType]);
const loadLogContent = useCallback(async (logPath, offset = 0) => {
try {
const data = await getLogContent(logPath, offset, PAGE_SIZE);
setLogContent(data.content || '');
setTotalLines(data.total_lines || 0);
setCurrentOffset(offset);
} catch (error) {
console.error('加载日志内容失败:', error);
alert(`加载日志内容失败:${error.response?.data?.detail || error.message}`);
}
}, []);
useEffect(() => {
loadLogs();
}, [loadLogs]);
const handleViewLog = async (log) => {
const handleViewLog = async log => {
setSelectedLog(log);
setShowModal(true);
setCurrentOffset(0);
@@ -37,83 +51,78 @@ const LogManagementPanel = ({ isAdmin }) => {
await loadLogContent(log.path, 0);
};
const loadLogContent = async (logPath, offset = 0) => {
try {
const data = await getLogContent(logPath, offset, 1000);
setLogContent(data.content);
setTotalLines(data.total_lines);
setCurrentOffset(offset);
} catch (error) {
console.error('加载日志内容失败:', error);
alert(`加载日志内容失败: ${error.response?.data?.detail || error.message}`);
}
};
const handleDeleteLog = async (log) => {
const handleDeleteLog = async log => {
if (!isAdmin) {
alert('只有管理员可以删除日志');
alert('只有管理员可以删除日志');
return;
}
if (!window.confirm(`确定要删除日志文件 "${log.name}" 吗?\n\n此操作不可恢复`)) {
if (!window.confirm(`确定要删除日志文件${log.name}吗?\n\n此操作不可恢复`)) {
return;
}
try {
await deleteLog(log.path);
alert('日志文件已删除');
loadLogs();
alert('日志文件已删除');
await loadLogs();
if (selectedLog && selectedLog.path === log.path) {
setShowModal(false);
setSelectedLog(null);
setLogContent('');
setTotalLines(0);
setCurrentOffset(0);
}
} catch (error) {
console.error('删除日志失败:', error);
alert(`删除日志失败: ${error.response?.data?.detail || error.message}`);
alert(`删除日志失败${error.response?.data?.detail || error.message}`);
}
};
const handlePrevPage = () => {
if (currentOffset > 0) {
const newOffset = Math.max(0, currentOffset - 1000);
if (selectedLog && currentOffset > 0) {
const newOffset = Math.max(0, currentOffset - PAGE_SIZE);
loadLogContent(selectedLog.path, newOffset);
}
};
const handleNextPage = () => {
if (currentOffset + 1000 < totalLines) {
const newOffset = currentOffset + 1000;
if (selectedLog && currentOffset + PAGE_SIZE < totalLines) {
const newOffset = currentOffset + PAGE_SIZE;
loadLogContent(selectedLog.path, newOffset);
}
};
const formatSize = (bytes) => {
const formatSize = bytes => {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
};
const getTypeLabel = (type) => {
const getTypeLabel = type => {
const labels = {
app: '应用日志',
task: '任务日志',
error: '错误日志',
other: '其他'
other: '其他',
};
return labels[type] || type;
};
const getTypeColor = (type) => {
const getTypeColor = type => {
const colors = {
app: '#3b82f6',
task: '#10b981',
error: '#ef4444',
other: '#6b7280'
other: '#6b7280',
};
return colors[type] || '#6b7280';
};
const filteredContent = searchTerm
? logContent.split('\n').filter(line => line.toLowerCase().includes(searchTerm.toLowerCase())).join('\n')
? logContent
.split('\n')
.filter(line => line.toLowerCase().includes(searchTerm.toLowerCase()))
.join('\n')
: logContent;
return (
@@ -121,10 +130,10 @@ const LogManagementPanel = ({ isAdmin }) => {
<div style={{ marginBottom: '20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h3 style={{ margin: 0 }}>日志管理</h3>
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
<label>类型过滤:</label>
<label>类型筛选</label>
<select
value={filterType}
onChange={(e) => setFilterType(e.target.value)}
onChange={event => setFilterType(event.target.value)}
style={{ padding: '5px 10px', borderRadius: '4px', border: '1px solid #ddd' }}
>
<option value="">全部</option>
@@ -141,7 +150,7 @@ const LogManagementPanel = ({ isAdmin }) => {
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: loading ? 'not-allowed' : 'pointer'
cursor: loading ? 'not-allowed' : 'pointer',
}}
>
{loading ? '加载中...' : '刷新'}
@@ -150,11 +159,16 @@ const LogManagementPanel = ({ isAdmin }) => {
</div>
{logs.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#6b7280' }}>
暂无日志文件
</div>
<div style={{ textAlign: 'center', padding: '40px', color: '#6b7280' }}>暂无日志文件</div>
) : (
<table style={{ width: '100%', borderCollapse: 'collapse', backgroundColor: 'white', boxShadow: '0 1px 3px rgba(0,0,0,0.1)' }}>
<table
style={{
width: '100%',
borderCollapse: 'collapse',
backgroundColor: 'white',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
}}
>
<thead>
<tr style={{ backgroundColor: '#f3f4f6', borderBottom: '2px solid #e5e7eb' }}>
<th style={{ padding: '12px', textAlign: 'left' }}>文件名</th>
@@ -169,13 +183,15 @@ const LogManagementPanel = ({ isAdmin }) => {
<tr key={index} style={{ borderBottom: '1px solid #e5e7eb' }}>
<td style={{ padding: '12px', fontFamily: 'monospace', fontSize: '13px' }}>{log.name}</td>
<td style={{ padding: '12px' }}>
<span style={{
padding: '2px 8px',
borderRadius: '12px',
fontSize: '12px',
backgroundColor: getTypeColor(log.type) + '20',
color: getTypeColor(log.type)
}}>
<span
style={{
padding: '2px 8px',
borderRadius: '12px',
fontSize: '12px',
backgroundColor: `${getTypeColor(log.type)}20`,
color: getTypeColor(log.type),
}}
>
{getTypeLabel(log.type)}
</span>
</td>
@@ -195,7 +211,7 @@ const LogManagementPanel = ({ isAdmin }) => {
borderRadius: '4px',
cursor: 'pointer',
fontSize: '13px',
whiteSpace: 'nowrap'
whiteSpace: 'nowrap',
}}
>
查看
@@ -211,7 +227,7 @@ const LogManagementPanel = ({ isAdmin }) => {
borderRadius: '4px',
cursor: 'pointer',
fontSize: '13px',
whiteSpace: 'nowrap'
whiteSpace: 'nowrap',
}}
>
删除
@@ -225,42 +241,46 @@ const LogManagementPanel = ({ isAdmin }) => {
</table>
)}
{/* 日志查看 Modal */}
{showModal && selectedLog && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 9999
}}>
<div style={{
backgroundColor: 'white',
borderRadius: '8px',
width: '90%',
maxWidth: '1200px',
maxHeight: '90vh',
<div
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
flexDirection: 'column',
boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)'
}}>
{/* Modal Header */}
<div style={{
padding: '20px',
borderBottom: '1px solid #e5e7eb',
alignItems: 'center',
justifyContent: 'center',
zIndex: 9999,
}}
>
<div
style={{
backgroundColor: 'white',
borderRadius: '8px',
width: '90%',
maxWidth: '1200px',
maxHeight: '90vh',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
flexDirection: 'column',
boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
}}
>
<div
style={{
padding: '20px',
borderBottom: '1px solid #e5e7eb',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<div>
<h3 style={{ margin: '0 0 8px 0', fontFamily: 'monospace' }}>{selectedLog.name}</h3>
<div style={{ fontSize: '13px', color: '#6b7280' }}>
大小: {formatSize(selectedLog.size)} | 修改时间: {selectedLog.modified_at} | 总行数: {totalLines}
大小{formatSize(selectedLog.size)} | 修改时间{selectedLog.modified_at} | 总行数{totalLines}
</div>
</div>
<button
@@ -271,30 +291,29 @@ const LogManagementPanel = ({ isAdmin }) => {
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
cursor: 'pointer',
}}
>
关闭
</button>
</div>
{/* Search Bar */}
<div style={{ padding: '12px 20px', borderBottom: '1px solid #e5e7eb', display: 'flex', gap: '10px', alignItems: 'center' }}>
<input
type="text"
placeholder="搜索日志内容..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
onChange={event => setSearchTerm(event.target.value)}
style={{
flex: 1,
padding: '6px 12px',
border: '1px solid #d1d5db',
borderRadius: '4px',
fontSize: '13px'
fontSize: '13px',
}}
/>
<div style={{ fontSize: '13px', color: '#6b7280' }}>
显示行: {currentOffset + 1} - {Math.min(currentOffset + 1000, totalLines)}
显示行 {totalLines === 0 ? 0 : currentOffset + 1} - {Math.min(currentOffset + PAGE_SIZE, totalLines)}
</div>
<button
onClick={handlePrevPage}
@@ -306,40 +325,41 @@ const LogManagementPanel = ({ isAdmin }) => {
border: 'none',
borderRadius: '4px',
cursor: currentOffset === 0 ? 'not-allowed' : 'pointer',
fontSize: '13px'
fontSize: '13px',
}}
>
上一页
</button>
<button
onClick={handleNextPage}
disabled={currentOffset + 1000 >= totalLines}
disabled={currentOffset + PAGE_SIZE >= totalLines}
style={{
padding: '6px 12px',
backgroundColor: currentOffset + 1000 >= totalLines ? '#e5e7eb' : '#3b82f6',
color: currentOffset + 1000 >= totalLines ? '#9ca3af' : 'white',
backgroundColor: currentOffset + PAGE_SIZE >= totalLines ? '#e5e7eb' : '#3b82f6',
color: currentOffset + PAGE_SIZE >= totalLines ? '#9ca3af' : 'white',
border: 'none',
borderRadius: '4px',
cursor: currentOffset + 1000 >= totalLines ? 'not-allowed' : 'pointer',
fontSize: '13px'
cursor: currentOffset + PAGE_SIZE >= totalLines ? 'not-allowed' : 'pointer',
fontSize: '13px',
}}
>
下一页
</button>
</div>
{/* Log Content */}
<div style={{ flex: 1, overflow: 'auto', padding: '20px', backgroundColor: '#1e1e1e' }}>
<pre style={{
margin: 0,
fontFamily: 'Consolas, Monaco, "Courier New", monospace',
fontSize: '12px',
lineHeight: '1.5',
color: '#d4d4d4',
whiteSpace: 'pre-wrap',
wordBreak: 'break-all'
}}>
{filteredContent || '(空日志)'}
<pre
style={{
margin: 0,
fontFamily: 'Consolas, Monaco, "Courier New", monospace',
fontSize: '12px',
lineHeight: '1.5',
color: '#d4d4d4',
whiteSpace: 'pre-wrap',
wordBreak: 'break-all',
}}
>
{filteredContent || '(空日志)'}
</pre>
</div>
</div>