diff --git a/internal/client/tunnel/client.go b/internal/client/tunnel/client.go index ebe98ae..0bb1cba 100644 --- a/internal/client/tunnel/client.go +++ b/internal/client/tunnel/client.go @@ -9,6 +9,7 @@ import ( "os/exec" "path/filepath" "runtime" + "strings" "sync" "time" @@ -248,6 +249,8 @@ func (c *Client) handleStream(stream net.Conn) { go c.handleLogRequest(stream, msg) case protocol.MsgTypeLogStop: c.handleLogStop(stream, msg) + case protocol.MsgTypePluginStatusQuery: + c.handlePluginStatusQuery(stream, msg) } } @@ -913,6 +916,30 @@ func restartClientProcess(path, serverAddr, token, id string) { os.Exit(0) } +// handlePluginStatusQuery 处理插件状态查询 +func (c *Client) handlePluginStatusQuery(stream net.Conn, msg *protocol.Message) { + defer stream.Close() + + c.pluginMu.RLock() + plugins := make([]protocol.PluginStatusEntry, 0, len(c.runningPlugins)) + for key := range c.runningPlugins { + // key 格式为 "pluginName:ruleName",只提取 pluginName + parts := strings.SplitN(key, ":", 2) + pluginName := parts[0] + plugins = append(plugins, protocol.PluginStatusEntry{ + PluginName: pluginName, + Running: true, + }) + } + c.pluginMu.RUnlock() + + resp := protocol.PluginStatusQueryResponse{ + Plugins: plugins, + } + respMsg, _ := protocol.NewMessage(protocol.MsgTypePluginStatusQueryResp, resp) + protocol.WriteMessage(stream, respMsg) +} + // handleLogRequest 处理日志请求 func (c *Client) handleLogRequest(stream net.Conn, msg *protocol.Message) { if c.logger == nil { diff --git a/internal/server/router/handler/client.go b/internal/server/router/handler/client.go index 3d861f5..bae7405 100644 --- a/internal/server/router/handler/client.go +++ b/internal/server/router/handler/client.go @@ -115,11 +115,39 @@ func (h *ClientHandler) Get(c *gin.Context) { online, lastPing, remoteAddr := h.app.GetServer().GetClientStatus(clientID) + // 复制插件列表 + plugins := make([]db.ClientPlugin, len(client.Plugins)) + copy(plugins, client.Plugins) + + // 如果客户端在线,获取实时插件运行状态 + if online { + if statusList, err := h.app.GetServer().GetClientPluginStatus(clientID); err == nil { + // 创建运行中插件的映射 + runningPlugins := make(map[string]bool) + for _, s := range statusList { + runningPlugins[s.PluginName] = s.Running + } + // 更新插件状态 + for i := range plugins { + if running, ok := runningPlugins[plugins[i].Name]; ok { + plugins[i].Running = running + } else { + plugins[i].Running = false + } + } + } + } else { + // 客户端离线时,所有插件都标记为未运行 + for i := range plugins { + plugins[i].Running = false + } + } + resp := dto.ClientResponse{ ID: client.ID, Nickname: client.Nickname, Rules: client.Rules, - Plugins: client.Plugins, + Plugins: plugins, Online: online, LastPing: lastPing, RemoteAddr: remoteAddr, diff --git a/internal/server/router/handler/interfaces.go b/internal/server/router/handler/interfaces.go index 71c1a51..dc8771d 100644 --- a/internal/server/router/handler/interfaces.go +++ b/internal/server/router/handler/interfaces.go @@ -45,6 +45,8 @@ type ServerInterface interface { // 日志流 StartClientLogStream(clientID, sessionID string, lines int, follow bool, level string) (<-chan protocol.LogEntry, error) StopClientLogStream(sessionID string) + // 插件状态查询 + GetClientPluginStatus(clientID string) ([]protocol.PluginStatusEntry, error) } // ConfigField 配置字段 diff --git a/internal/server/tunnel/server.go b/internal/server/tunnel/server.go index 28619d5..b1080e4 100644 --- a/internal/server/tunnel/server.go +++ b/internal/server/tunnel/server.go @@ -574,6 +574,49 @@ func (s *Server) GetClientStatus(clientID string) (online bool, lastPing string, return false, "", "" } +// GetClientPluginStatus 获取客户端插件运行状态 +func (s *Server) GetClientPluginStatus(clientID string) ([]protocol.PluginStatusEntry, error) { + s.mu.RLock() + cs, ok := s.clients[clientID] + s.mu.RUnlock() + + if !ok { + return nil, fmt.Errorf("client %s not online", clientID) + } + + stream, err := cs.Session.Open() + if err != nil { + return nil, err + } + defer stream.Close() + + // 发送查询请求 + msg, err := protocol.NewMessage(protocol.MsgTypePluginStatusQuery, nil) + if err != nil { + return nil, err + } + if err := protocol.WriteMessage(stream, msg); err != nil { + return nil, err + } + + // 读取响应 + resp, err := protocol.ReadMessage(stream) + if err != nil { + return nil, err + } + + if resp.Type != protocol.MsgTypePluginStatusQueryResp { + return nil, fmt.Errorf("unexpected response type: %d", resp.Type) + } + + var statusResp protocol.PluginStatusQueryResponse + if err := resp.ParsePayload(&statusResp); err != nil { + return nil, err + } + + return statusResp.Plugins, nil +} + // GetAllClientStatus 获取所有客户端状态 func (s *Server) GetAllClientStatus() map[string]struct { Online bool diff --git a/pkg/protocol/message.go b/pkg/protocol/message.go index 6fb0136..0378684 100644 --- a/pkg/protocol/message.go +++ b/pkg/protocol/message.go @@ -40,10 +40,12 @@ const ( MsgTypePluginConfig uint8 = 25 // 插件配置同步 // 客户端插件消息 - MsgTypeClientPluginStart uint8 = 40 // 启动客户端插件 - MsgTypeClientPluginStop uint8 = 41 // 停止客户端插件 - MsgTypeClientPluginStatus uint8 = 42 // 客户端插件状态 - MsgTypeClientPluginConn uint8 = 43 // 客户端插件连接请求 + MsgTypeClientPluginStart uint8 = 40 // 启动客户端插件 + MsgTypeClientPluginStop uint8 = 41 // 停止客户端插件 + MsgTypeClientPluginStatus uint8 = 42 // 客户端插件状态 + MsgTypeClientPluginConn uint8 = 43 // 客户端插件连接请求 + MsgTypePluginStatusQuery uint8 = 44 // 查询所有插件状态 + MsgTypePluginStatusQueryResp uint8 = 45 // 插件状态查询响应 // JS 插件动态安装 MsgTypeJSPluginInstall uint8 = 50 // 安装 JS 插件 @@ -225,6 +227,17 @@ type ClientPluginConnRequest struct { RuleName string `json:"rule_name"` // 规则名称 } +// PluginStatusEntry 单个插件状态 +type PluginStatusEntry struct { + PluginName string `json:"plugin_name"` // 插件名称 + Running bool `json:"running"` // 是否运行中 +} + +// PluginStatusQueryResponse 插件状态查询响应 +type PluginStatusQueryResponse struct { + Plugins []PluginStatusEntry `json:"plugins"` // 所有插件状态 +} + // JSPluginInstallRequest JS 插件安装请求 type JSPluginInstallRequest struct { PluginName string `json:"plugin_name"` // 插件名称