Skip to content

Commit 9d33418

Browse files
committed
fix: 实例详情页清除日式失效问题。
1 parent ba53bec commit 9d33418

File tree

8 files changed

+134
-221
lines changed

8 files changed

+134
-221
lines changed

internal/api/endpoint.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,7 +1167,7 @@ func (h *EndpointHandler) refreshTunnels(endpointID int64) error {
11671167
if err = tx.Where("tunnel_id = ?", tunnel.ID).Delete(&models.TunnelOperationLog{}).Error; err != nil {
11681168
log.Warnf("[API] 删除隧道 %d 操作日志失败: %v", tunnel.ID, err)
11691169
}
1170-
1170+
11711171
if err = tx.Delete(&models.Tunnel{}, tunnel.ID).Error; err != nil {
11721172
return fmt.Errorf("删除隧道失败: %v", err)
11731173
}
@@ -1445,9 +1445,9 @@ func (h *EndpointHandler) HandleEndpointFileLogs(c *gin.Context) {
14451445
return
14461446
}
14471447

1448-
// 转换为LogEntry格式以保持兼容性
1449-
var logEntries []log.LogEntry
1450-
for _, logLine := range logs {
1448+
// 转换为统一的日志格式
1449+
var logEntries []map[string]interface{}
1450+
for i, logLine := range logs {
14511451
if logLine != "" {
14521452
// 尝试解析日志行中的时间戳
14531453
var timestamp time.Time
@@ -1462,10 +1462,13 @@ func (h *EndpointHandler) HandleEndpointFileLogs(c *gin.Context) {
14621462
timestamp = targetDate // 如果没有时间戳,使用目标日期
14631463
}
14641464

1465-
logEntries = append(logEntries, log.LogEntry{
1466-
Timestamp: timestamp,
1467-
Content: logLine,
1468-
FilePath: fmt.Sprintf("%s.log", targetDate.Format("2006-01-02")),
1465+
logEntries = append(logEntries, map[string]interface{}{
1466+
"id": i + 1,
1467+
"message": processAnsiColors(logLine), // 处理ANSI颜色
1468+
"content": processAnsiColors(logLine), // 保持向后兼容
1469+
"isHtml": true,
1470+
"timestamp": timestamp,
1471+
"filePath": fmt.Sprintf("%s.log", targetDate.Format("2006-01-02")),
14691472
})
14701473
}
14711474
}

internal/api/tunnel.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ func SetupTunnelRoutes(rg *gin.RouterGroup, tunnelService *tunnel.Service, sseMa
7272
rg.POST("/tunnels/:id/action", tunnelHandler.HandleControlTunnel)
7373
rg.GET("/tunnels/:id/details", tunnelHandler.HandleGetTunnelDetails)
7474
rg.GET("/tunnels/:id/file-logs", tunnelHandler.HandleTunnelFileLogs)
75+
rg.DELETE("/tunnels/:id/file-logs/clear", tunnelHandler.HandleClearTunnelFileLogs)
7576
rg.GET("/tunnels/:id/traffic-trend", tunnelHandler.HandleGetTunnelTrafficTrend)
7677
rg.GET("/tunnels/:id/ping-trend", tunnelHandler.HandleGetTunnelPingTrend)
7778
rg.GET("/tunnels/:id/pool-trend", tunnelHandler.HandleGetTunnelPoolTrend)
@@ -1441,6 +1442,47 @@ func (h *TunnelHandler) HandleTunnelFileLogs(c *gin.Context) {
14411442
})
14421443
}
14431444

1445+
// HandleClearTunnelFileLogs 清空指定隧道的文件日志 (DELETE /api/tunnels/{id}/file-logs/clear)
1446+
func (h *TunnelHandler) HandleClearTunnelFileLogs(c *gin.Context) {
1447+
idStr := c.Param("id")
1448+
if idStr == "" {
1449+
c.JSON(http.StatusBadRequest, gin.H{"error": "缺少隧道ID"})
1450+
return
1451+
}
1452+
1453+
db := h.tunnelService.DB()
1454+
1455+
// 查询隧道获得 endpointId
1456+
var endpointID int64
1457+
if err := db.QueryRow(`SELECT endpoint_id FROM tunnels WHERE instance_id = ?`, idStr).Scan(&endpointID); err != nil {
1458+
if err == sql.ErrNoRows {
1459+
c.JSON(http.StatusNotFound, gin.H{"error": "隧道不存在"})
1460+
return
1461+
}
1462+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1463+
return
1464+
}
1465+
1466+
// 检查是否有FileLogger
1467+
if h.sseManager == nil || h.sseManager.GetFileLogger() == nil {
1468+
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "文件日志服务不可用"})
1469+
return
1470+
}
1471+
1472+
// 清空文件日志
1473+
err := h.sseManager.GetFileLogger().ClearLogs(endpointID, idStr)
1474+
if err != nil {
1475+
log.Warnf("[API]清空文件日志失败: %v", err)
1476+
c.JSON(http.StatusInternalServerError, gin.H{"error": "清空日志失败"})
1477+
return
1478+
}
1479+
1480+
c.JSON(http.StatusOK, map[string]interface{}{
1481+
"success": true,
1482+
"message": "文件日志已清空",
1483+
})
1484+
}
1485+
14441486
// processAnsiColors 将 ANSI 颜色码转换为 HTML span
14451487
func processAnsiColors(text string) string {
14461488
// 移除时间戳前缀(可选)

internal/sse/service.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -490,34 +490,62 @@ func (s *Service) updateEndpointTunnelCount(endpointID int64) {
490490
// sendTunnelUpdateByInstanceId 根据实例ID发送隧道更新
491491
func (s *Service) sendTunnelUpdateByInstanceId(instanceID string, data SSEResp) {
492492
s.mu.RLock()
493-
defer s.mu.RUnlock()
494-
495493
subscribers, exists := s.tunnelSubs[instanceID]
496494
if !exists {
495+
s.mu.RUnlock()
497496
// log.Debug("[SSE]隧道 %s 没有订阅者", instanceID)
498497
return
499498
}
500499

500+
// 创建订阅者副本,避免在遍历时修改map
501+
clientList := make([]*Client, 0, len(subscribers))
502+
clientIDs := make([]string, 0, len(subscribers))
503+
for clientID, client := range subscribers {
504+
clientList = append(clientList, client)
505+
clientIDs = append(clientIDs, clientID)
506+
}
507+
s.mu.RUnlock()
508+
501509
jsonData, err := json.Marshal(data)
502510
if err != nil {
503511
log.Errorf("序列化隧道数据失败: %v", err)
504512
return
505513
}
506514

507-
for clientID, client := range subscribers {
515+
// 记录需要删除的客户端
516+
disconnectedClients := make([]string, 0)
517+
518+
for i, client := range clientList {
519+
clientID := clientIDs[i]
508520
if err := client.Send(jsonData); err != nil {
509521
// 检查是否是连接断开错误,使用更合适的日志级别
510522
if client.IsDisconnected() {
511-
log.Warnf("[SSE]客户端 %s 连接已断开,移除订阅", clientID)
512-
// 从订阅列表中移除已断开的客户端
513-
delete(subscribers, clientID)
523+
log.Warnf("[SSE]客户端 %s 连接已断开,标记移除订阅", clientID)
524+
disconnectedClients = append(disconnectedClients, clientID)
514525
} else {
515526
log.Errorf("发送隧道更新给客户端 %s 失败: %v", clientID, err)
516527
}
517528
} else {
518529
log.Debugf("[SSE]隧道 %s 的订阅者 %s 推送成功", instanceID, clientID)
519530
}
520531
}
532+
533+
// 如果有断开的客户端,获取写锁并移除它们
534+
if len(disconnectedClients) > 0 {
535+
s.mu.Lock()
536+
if subscribers, exists := s.tunnelSubs[instanceID]; exists {
537+
for _, clientID := range disconnectedClients {
538+
delete(subscribers, clientID)
539+
log.Infof("[SSE]已移除断开连接的客户端 %s from tunnel %s", clientID, instanceID)
540+
}
541+
// 如果订阅者列表为空,删除整个条目
542+
if len(subscribers) == 0 {
543+
delete(s.tunnelSubs, instanceID)
544+
log.Debugf("[SSE]隧道 %s 无订阅者,清理订阅列表", instanceID)
545+
}
546+
}
547+
s.mu.Unlock()
548+
}
521549
}
522550

523551
// setTunnelsOfflineForEndpoint 将端点的所有隧道设置为离线状态

web/src/components/layout/navbar-logo.tsx

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NavbarBrand, Link, cn, Badge } from "@heroui/react";
1+
import { NavbarBrand, Link, cn, Chip } from "@heroui/react";
22
import { useTheme } from "next-themes";
33
import { useIsSSR } from "@react-aria/ssr";
44

@@ -50,7 +50,7 @@ export const NavbarLogo = () => {
5050
// 确定badge内容和颜色
5151
const getBadgeProps = () => {
5252
if (isDev) {
53-
return { content: "dev", color: "secondary" as const };
53+
return { content: "dev", color: "default" as const };
5454
}
5555
if (isBeta) {
5656
return { content: "beta", color: "primary" as const };
@@ -62,28 +62,15 @@ export const NavbarLogo = () => {
6262

6363
return (
6464
<NavbarBrand as="li" className="gap-3 max-w-fit">
65-
<Link className="flex justify-start items-center gap-2" href="/">
66-
{badgeProps ? (
67-
<>
68-
<NodePassLogo />
69-
<Badge
70-
content={badgeProps.content}
71-
color={badgeProps.color}
72-
variant="flat"
73-
size="sm"
74-
>
75-
<p className={cn("font-bold text-foreground", fontSans.className)}>
76-
NodePassDash
77-
</p>
78-
</Badge>
79-
</>
80-
) : (
81-
<>
82-
<NodePassLogo />
83-
<p className={cn("font-bold text-foreground", fontSans.className)}>
84-
NodePassDash
85-
</p>
86-
</>
65+
<Link className="flex justify-start items-center" href="/">
66+
<NodePassLogo />
67+
<p className={cn("font-bold text-foreground pl-1", fontSans.className)}>
68+
NodePassDash
69+
</p>
70+
{badgeProps && (
71+
<Chip variant="flat" color={badgeProps.color} size="sm" className="h-5 p-0 ml-1">
72+
{badgeProps.content}
73+
</Chip>
8774
)}
8875
</Link>
8976
</NavbarBrand>

web/src/components/ui/file-log-viewer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import React, { useState, useEffect, useRef, useCallback } from "react";
44
import { addToast } from "@heroui/toast";
5+
import { processAnsiColors } from "@/lib/utils/ansi";
56

67
interface FileLogEntry {
78
timestamp: string;
@@ -209,7 +210,7 @@ export const FileLogViewer: React.FC<FileLogViewerProps> = ({
209210
const lines = logContent.split("\n").filter((line) => line.length > 0);
210211
const newLogEntries: FileLogEntry[] = lines.map((line, index) => ({
211212
timestamp: new Date().toISOString(),
212-
content: line,
213+
content: processAnsiColors(line), // 处理ANSI颜色
213214
filePath: "live", // 标记为实时日志
214215
}));
215216

0 commit comments

Comments
 (0)