Skip to content

Commit 884a3d4

Browse files
author
yangtao
committed
feat(tunnels): 优化日志处理和显示
- 移除 ANSI 转义字符,保留颜色代码 - 增加日志对象格式支持,包含时间信息 - 优化日志渲染逻辑,处理不同格式的日志数据 - 更新 footer 版权信息 - 修正 GitHub 链接
1 parent 2b61c0b commit 884a3d4

File tree

9 files changed

+68
-42
lines changed

9 files changed

+68
-42
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 🚀 NodePass WebUI
22

3-
![Version](https://img.shields.io/badge/version-1.2.1-blue.svg)
3+
![Version](https://img.shields.io/badge/version-1.2.2-blue.svg)
44

55
一个现代化的 NodePass 管理界面,基于 Next.js 14、HeroUI 和 TypeScript 构建。提供实时隧道监控、流量统计和端点管理功能。
66

app/api/tunnels/[id]/details/route.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ const processAnsiColors = (text: string) => {
1717
// 移除时间戳前缀(如果存在)
1818
text = text.replace(/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\s/, '');
1919

20+
// 只移除 \u001B 字符,保留后面的颜色代码
21+
text = text.replace(/\u001B/g, ''); // 只移除 ESC 字符,保留 [32m 等
22+
2023
// 将 ANSI 颜色代码转换为 HTML span 标签
2124
const colorMap = new Map([
2225
[/\[32m/g, '<span class="text-green-400">'],
@@ -91,7 +94,9 @@ export async function GET(
9194
},
9295
take: 200, // 获取最近200条日志
9396
select: {
94-
logs: true
97+
logs: true,
98+
eventTime: true,
99+
createdAt: true
95100
}
96101
}) : [];
97102

@@ -162,10 +167,21 @@ export async function GET(
162167
commandLine: tunnel.commandLine
163168
};
164169

165-
// 6. 处理日志数据 - 应用ANSI颜色处理
170+
// 6. 处理日志数据 - 应用ANSI颜色处理,保留时间信息
166171
const logs = logRecords
167172
.filter(record => record.logs && record.logs.trim()) // 过滤空日志
168-
.map(log => processAnsiColors(log.logs || '')); // 处理ANSI颜色并返回字符串
173+
.map((record, index) => ({
174+
id: index + 1,
175+
message: processAnsiColors(record.logs || ''),
176+
isHtml: true,
177+
traffic: {
178+
tcpRx: 0,
179+
tcpTx: 0,
180+
udpRx: 0,
181+
udpTx: 0
182+
},
183+
timestamp: record.eventTime || record.createdAt // 使用事件时间或创建时间
184+
}));
169185

170186
return NextResponse.json({
171187
tunnelInfo,

app/api/tunnels/[id]/logs/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ export async function GET(
8080
// 移除时间戳前缀(如果存在)
8181
text = text.replace(/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\s/, '');
8282

83+
// 只移除 \u001B 字符,保留后面的颜色代码
84+
text = text.replace(/\u001B/g, ''); // 只移除 ESC 字符,保留 [32m 等
85+
8386
// 将 ANSI 颜色代码转换为对象
8487
const colorMap = new Map([
8588
[/\[32m/g, '<span class="text-green-400">'],

app/client-layout.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { usePathname } from 'next/navigation';
88
import { AuthProvider } from "./components/auth-provider";
99
import { RouteGuard } from "./components/route-guard";
1010
import { Navbar } from "@/components/layout/navbar";
11+
import pkg from '../package.json';
1112

1213
export function ClientLayout({ children }: { children: React.ReactNode }) {
1314
const pathname = usePathname();
@@ -29,15 +30,9 @@ export function ClientLayout({ children }: { children: React.ReactNode }) {
2930
{children}
3031
</main>
3132
<footer className="w-full flex items-center justify-center py-3">
32-
{/* <Link
33-
isExternal
34-
className="flex items-center gap-1 text-current"
35-
href="https://heroui.com?utm_source=next-app-template"
36-
title="heroui.com homepage"
37-
>
38-
<span className="text-default-600">Powered by</span>
39-
<p className="text-primary">HeroUI</p>
40-
</Link> */}
33+
<div className="text-default-600 text-sm">
34+
NodePass管理 © 2025 | v{pkg.version} | 由 <a href="https://github.com/yosebyte/nodepass" target="_blank" className="text-blue-500 hover:text-blue-600">NodePass</a> 驱动
35+
</div>
4136
</footer>
4237
</div>
4338
)}

app/tunnels/[id]/page.tsx

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,19 @@ const processAnsiColors = (text: string) => {
3434
// 移除时间戳前缀(如果存在)
3535
text = text.replace(/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\s/, '');
3636

37+
// 只移除 \u001B 字符,保留后面的颜色代码
38+
text = text.replace(/\u001B/g, ''); // 只移除 ESC 字符,保留 [32m 等
39+
3740
// 将 ANSI 颜色代码转换为 HTML span 标签
3841
const colorMap = new Map([
39-
[/\[32m/g, '<span class="text-green-400">'],
40-
[/\[31m/g, '<span class="text-red-400">'],
41-
[/\[33m/g, '<span class="text-yellow-400">'],
42-
[/\[34m/g, '<span class="text-blue-400">'],
43-
[/\[35m/g, '<span class="text-purple-400">'],
44-
[/\[36m/g, '<span class="text-cyan-400">'],
45-
[/\[37m/g, '<span class="text-gray-400">'],
46-
[/\[0m/g, '</span>']
42+
[/\[32m/g, '<span class="text-green-400">'], // INFO - 绿色
43+
[/\[31m/g, '<span class="text-red-400">'], // ERROR - 红色
44+
[/\[33m/g, '<span class="text-yellow-400">'], // WARN - 黄色
45+
[/\[34m/g, '<span class="text-blue-400">'], // DEBUG - 蓝色
46+
[/\[35m/g, '<span class="text-purple-400">'], // 紫色
47+
[/\[36m/g, '<span class="text-cyan-400">'], // 青色
48+
[/\[37m/g, '<span class="text-gray-400">'], // 灰色
49+
[/\[0m/g, '</span>'] // 结束标签
4750
]);
4851

4952
// 替换颜色代码
@@ -55,15 +58,16 @@ const processAnsiColors = (text: string) => {
5558
const openTags = (text.match(/<span/g) || []).length;
5659
const closeTags = (text.match(/<\/span>/g) || []).length;
5760

58-
// 如果开标签比闭标签多,添加缺少的闭标签
61+
// 如果开始标签多于结束标签,添加结束标签
5962
if (openTags > closeTags) {
60-
text += '</span>'.repeat(openTags - closeTags);
63+
const missingCloseTags = openTags - closeTags;
64+
text += '</span>'.repeat(missingCloseTags);
6165
}
6266

6367
return text;
6468
} catch (error) {
65-
console.error('处理日志颜色失败:', error);
66-
return text; // 如果处理失败,返回原始文本
69+
console.error('处理ANSI颜色失败:', error);
70+
return text;
6771
}
6872
};
6973

@@ -383,23 +387,31 @@ export default function TunnelDetailPage({ params }: { params: Promise<PageParam
383387
完整数据: JSON.stringify(data.tunnelInfo, null, 2)
384388
});
385389

386-
// 设置历史日志 - 处理字符串数组格式,启用HTML渲染
390+
// 设置历史日志 - 处理带时间信息的日志对象
387391
if (data.logs && Array.isArray(data.logs)) {
388392
// 初始化计数器为历史日志的数量,确保新日志ID不会与历史日志冲突
389393
logCounterRef.current = data.logs.length;
390-
const formattedLogs = data.logs.map((message: string, index: number) => ({
391-
id: index + 1, // 从1开始计数,避免0值
392-
message,
393-
isHtml: true, // 启用HTML格式渲染
394-
traffic: {
395-
tcpRx: 0,
396-
tcpTx: 0,
397-
udpRx: 0,
398-
udpTx: 0
399-
},
400-
timestamp: new Date() // 使用当前时间作为占位符
401-
}));
402-
setLogs(formattedLogs);
394+
395+
// 检查日志数据格式
396+
if (data.logs.length > 0 && typeof data.logs[0] === 'object') {
397+
// 新格式:对象数组,包含时间信息
398+
setLogs(data.logs);
399+
} else {
400+
// 旧格式:字符串数组,需要转换
401+
const formattedLogs = data.logs.map((message: string, index: number) => ({
402+
id: index + 1,
403+
message,
404+
isHtml: true,
405+
traffic: {
406+
tcpRx: 0,
407+
tcpTx: 0,
408+
udpRx: 0,
409+
udpTx: 0
410+
},
411+
timestamp: new Date() // 使用当前时间作为占位符
412+
}));
413+
setLogs(formattedLogs);
414+
}
403415

404416
// 稍微延迟滚动,确保DOM更新完成
405417
setTimeout(scrollToBottom, 100);

components/layout/navbar-social.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const socialLinks = [
1313
key: "github",
1414
label: "Github",
1515
icon: "mdi:github",
16-
href: "https://github.com/yosebyte/nodepass",
16+
href: "https://github.com/Mecozea/nodepass-webui",
1717
target: "_blank",
1818
},
1919
{

docs/DOCKER.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ volumes:
115115
docker pull ghcr.io/mecozea/nodepass-webui:latest
116116

117117
# 特定版本
118-
docker pull ghcr.io/mecozea/nodepass-webui:v1.2.1
118+
docker pull ghcr.io/mecozea/nodepass-webui:v1.2.2
119119
```
120120

121121
## 🐛 故障排除

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nodepass-webui",
3-
"version": "1.2.1",
3+
"version": "1.2.2",
44
"private": true,
55
"scripts": {
66
"dev": "tsx server.ts",

public/sqlite.db

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)