feat(app): 添加服务端和客户端更新功能以及系统状态监控
Some checks failed
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-frontend (push) Has been cancelled
Some checks failed
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-frontend (push) Has been cancelled
- 在App.vue中新增服务端更新模态框和相关功能 - 添加applyServerUpdate API调用和更新确认对话框 - 实现客户端版本信息显示和更新检测功能 - 添加系统状态监控包括CPU、内存和磁盘使用情况 - 新增getClientSystemStats API接口获取客户端系统统计信息 - 更新go.mod和go.sum文件添加必要的依赖包 - 优化UI界面样式和交互体验
This commit is contained in:
@@ -19,6 +19,8 @@ import (
|
||||
"github.com/gotunnel/pkg/protocol"
|
||||
"github.com/gotunnel/pkg/relay"
|
||||
"github.com/gotunnel/pkg/update"
|
||||
"github.com/gotunnel/pkg/utils"
|
||||
"github.com/gotunnel/pkg/version"
|
||||
"github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
@@ -181,6 +183,7 @@ func (c *Client) connect() error {
|
||||
Token: c.Token,
|
||||
OS: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
Version: version.Version,
|
||||
}
|
||||
msg, _ := protocol.NewMessage(protocol.MsgTypeAuth, authReq)
|
||||
if err := protocol.WriteMessage(conn, msg); err != nil {
|
||||
@@ -286,6 +289,8 @@ func (c *Client) handleStream(stream net.Conn) {
|
||||
c.handlePluginStatusQuery(stream, msg)
|
||||
case protocol.MsgTypePluginAPIRequest:
|
||||
c.handlePluginAPIRequest(stream, msg)
|
||||
case protocol.MsgTypeSystemStatsRequest:
|
||||
c.handleSystemStatsRequest(stream, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1173,3 +1178,27 @@ func (c *Client) sendPluginAPIResponse(stream net.Conn, status int, headers map[
|
||||
msg, _ := protocol.NewMessage(protocol.MsgTypePluginAPIResponse, resp)
|
||||
protocol.WriteMessage(stream, msg)
|
||||
}
|
||||
|
||||
// handleSystemStatsRequest 处理系统状态请求
|
||||
func (c *Client) handleSystemStatsRequest(stream net.Conn, msg *protocol.Message) {
|
||||
defer stream.Close()
|
||||
|
||||
stats, err := utils.GetSystemStats()
|
||||
if err != nil {
|
||||
log.Printf("[Client] Failed to get system stats: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := protocol.SystemStatsResponse{
|
||||
CPUUsage: stats.CPUUsage,
|
||||
MemoryTotal: stats.MemoryTotal,
|
||||
MemoryUsed: stats.MemoryUsed,
|
||||
MemoryUsage: stats.MemoryUsage,
|
||||
DiskTotal: stats.DiskTotal,
|
||||
DiskUsed: stats.DiskUsed,
|
||||
DiskUsage: stats.DiskUsage,
|
||||
}
|
||||
|
||||
respMsg, _ := protocol.NewMessage(protocol.MsgTypeSystemStatsResponse, resp)
|
||||
protocol.WriteMessage(stream, respMsg)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ type ClientResponse struct {
|
||||
RemoteAddr string `json:"remote_addr,omitempty" example:"192.168.1.100:54321"`
|
||||
OS string `json:"os,omitempty" example:"linux"`
|
||||
Arch string `json:"arch,omitempty" example:"amd64"`
|
||||
Version string `json:"version,omitempty" example:"1.0.0"`
|
||||
}
|
||||
|
||||
// ClientListItem 客户端列表项
|
||||
|
||||
@@ -115,7 +115,7 @@ func (h *ClientHandler) Get(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
online, lastPing, remoteAddr, clientOS, clientArch := h.app.GetServer().GetClientStatus(clientID)
|
||||
online, lastPing, remoteAddr, clientOS, clientArch, clientVersion := h.app.GetServer().GetClientStatus(clientID)
|
||||
|
||||
// 复制插件列表
|
||||
plugins := make([]db.ClientPlugin, len(client.Plugins))
|
||||
@@ -155,6 +155,7 @@ func (h *ClientHandler) Get(c *gin.Context) {
|
||||
RemoteAddr: remoteAddr,
|
||||
OS: clientOS,
|
||||
Arch: clientArch,
|
||||
Version: clientVersion,
|
||||
}
|
||||
|
||||
Success(c, resp)
|
||||
@@ -241,7 +242,7 @@ func (h *ClientHandler) Delete(c *gin.Context) {
|
||||
func (h *ClientHandler) PushConfig(c *gin.Context) {
|
||||
clientID := c.Param("id")
|
||||
|
||||
online, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||
online, _, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||
if !online {
|
||||
ClientNotOnline(c)
|
||||
return
|
||||
@@ -310,7 +311,7 @@ func (h *ClientHandler) Restart(c *gin.Context) {
|
||||
func (h *ClientHandler) InstallPlugins(c *gin.Context) {
|
||||
clientID := c.Param("id")
|
||||
|
||||
online, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||
online, _, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||
if !online {
|
||||
ClientNotOnline(c)
|
||||
return
|
||||
@@ -450,6 +451,17 @@ func (h *ClientHandler) deleteClientPlugin(clientID, pluginID string) error {
|
||||
return h.app.GetClientStore().UpdateClient(client)
|
||||
}
|
||||
|
||||
// GetSystemStats 获取客户端系统状态
|
||||
func (h *ClientHandler) GetSystemStats(c *gin.Context) {
|
||||
clientID := c.Param("id")
|
||||
stats, err := h.app.GetServer().GetClientSystemStats(clientID)
|
||||
if err != nil {
|
||||
ClientNotOnline(c)
|
||||
return
|
||||
}
|
||||
Success(c, stats)
|
||||
}
|
||||
|
||||
// validateClientID 验证客户端 ID 格式
|
||||
func validateClientID(id string) bool {
|
||||
if len(id) < 1 || len(id) > 64 {
|
||||
|
||||
@@ -19,13 +19,14 @@ type AppInterface interface {
|
||||
|
||||
// ServerInterface 服务端接口
|
||||
type ServerInterface interface {
|
||||
GetClientStatus(clientID string) (online bool, lastPing, remoteAddr, clientOS, clientArch string)
|
||||
GetClientStatus(clientID string) (online bool, lastPing, remoteAddr, clientOS, clientArch, clientVersion string)
|
||||
GetAllClientStatus() map[string]struct {
|
||||
Online bool
|
||||
LastPing string
|
||||
RemoteAddr string
|
||||
OS string
|
||||
Arch string
|
||||
Version string
|
||||
}
|
||||
ReloadConfig() error
|
||||
GetBindAddr() string
|
||||
@@ -57,6 +58,8 @@ type ServerInterface interface {
|
||||
IsPortAvailable(port int, excludeClientID string) bool
|
||||
// 插件 API 代理
|
||||
ProxyPluginAPIRequest(clientID string, req protocol.PluginAPIRequest) (*protocol.PluginAPIResponse, error)
|
||||
// 系统状态
|
||||
GetClientSystemStats(clientID string) (*protocol.SystemStatsResponse, error)
|
||||
}
|
||||
|
||||
// ConfigField 配置字段
|
||||
|
||||
@@ -177,7 +177,7 @@ func (h *JSPluginHandler) PushToClient(c *gin.Context) {
|
||||
c.ShouldBindJSON(&pushReq) // 忽略错误,允许空请求体
|
||||
|
||||
// 检查客户端是否在线
|
||||
online, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||
online, _, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||
if !online {
|
||||
ClientNotOnline(c)
|
||||
return
|
||||
|
||||
@@ -35,7 +35,7 @@ func (h *LogHandler) StreamLogs(c *gin.Context) {
|
||||
clientID := c.Param("id")
|
||||
|
||||
// 检查客户端是否在线
|
||||
online, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||
online, _, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||
if !online {
|
||||
c.JSON(400, gin.H{"code": 400, "message": "client not online"})
|
||||
return
|
||||
|
||||
@@ -371,7 +371,7 @@ func (h *PluginHandler) UpdateClientConfig(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 如果客户端在线,同步配置
|
||||
online, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||
online, _, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||
if online {
|
||||
if err := h.app.GetServer().SyncPluginConfigToClient(clientID, pluginName, req.Config); err != nil {
|
||||
PartialSuccess(c, gin.H{"status": "partial", "port_changed": portChanged}, "config saved but sync failed: "+err.Error())
|
||||
|
||||
@@ -45,7 +45,7 @@ func (h *PluginAPIHandler) ProxyRequest(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 检查客户端是否在线
|
||||
online, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||
online, _, _, _, _, _ := h.app.GetServer().GetClientStatus(clientID)
|
||||
if !online {
|
||||
ClientNotOnline(c)
|
||||
return
|
||||
|
||||
@@ -82,7 +82,7 @@ func (h *StoreHandler) Install(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 检查客户端是否在线
|
||||
online, _, _, _, _ := h.app.GetServer().GetClientStatus(req.ClientID)
|
||||
online, _, _, _, _, _ := h.app.GetServer().GetClientStatus(req.ClientID)
|
||||
if !online {
|
||||
ClientNotOnline(c)
|
||||
return
|
||||
|
||||
@@ -69,6 +69,7 @@ func (r *GinRouter) SetupRoutes(app handler.AppInterface, jwtAuth *auth.JWTAuth,
|
||||
api.POST("/client/:id/restart", clientHandler.Restart)
|
||||
api.POST("/client/:id/install-plugins", clientHandler.InstallPlugins)
|
||||
api.POST("/client/:id/plugin/:pluginID/:action", clientHandler.PluginAction)
|
||||
api.GET("/client/:id/system-stats", clientHandler.GetSystemStats)
|
||||
|
||||
// 配置管理
|
||||
configHandler := handler.NewConfigHandler(app)
|
||||
|
||||
@@ -86,6 +86,7 @@ type ClientSession struct {
|
||||
RemoteAddr string // 客户端 IP 地址
|
||||
OS string // 客户端操作系统
|
||||
Arch string // 客户端架构
|
||||
Version string // 客户端版本
|
||||
Session *yamux.Session
|
||||
Rules []protocol.ProxyRule
|
||||
Listeners map[int]net.Listener
|
||||
@@ -289,11 +290,11 @@ func (s *Server) handleConnection(conn net.Conn) {
|
||||
}
|
||||
|
||||
security.LogAuthSuccess(clientIP, clientID)
|
||||
s.setupClientSession(conn, clientID, authReq.OS, authReq.Arch, rules)
|
||||
s.setupClientSession(conn, clientID, authReq.OS, authReq.Arch, authReq.Version, rules)
|
||||
}
|
||||
|
||||
// setupClientSession 建立客户端会话
|
||||
func (s *Server) setupClientSession(conn net.Conn, clientID, clientOS, clientArch string, rules []protocol.ProxyRule) {
|
||||
func (s *Server) setupClientSession(conn net.Conn, clientID, clientOS, clientArch, clientVersion string, rules []protocol.ProxyRule) {
|
||||
session, err := yamux.Server(conn, nil)
|
||||
if err != nil {
|
||||
log.Printf("[Server] Yamux error: %v", err)
|
||||
@@ -311,6 +312,7 @@ func (s *Server) setupClientSession(conn net.Conn, clientID, clientOS, clientArc
|
||||
RemoteAddr: remoteAddr,
|
||||
OS: clientOS,
|
||||
Arch: clientArch,
|
||||
Version: clientVersion,
|
||||
Session: session,
|
||||
Rules: rules,
|
||||
Listeners: make(map[int]net.Listener),
|
||||
@@ -571,16 +573,16 @@ func (s *Server) sendHeartbeat(cs *ClientSession) bool {
|
||||
}
|
||||
|
||||
// GetClientStatus 获取客户端状态
|
||||
func (s *Server) GetClientStatus(clientID string) (online bool, lastPing, remoteAddr, clientOS, clientArch string) {
|
||||
func (s *Server) GetClientStatus(clientID string) (online bool, lastPing, remoteAddr, clientOS, clientArch, clientVersion string) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
if cs, ok := s.clients[clientID]; ok {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
return true, cs.LastPing.Format(time.RFC3339), cs.RemoteAddr, cs.OS, cs.Arch
|
||||
return true, cs.LastPing.Format(time.RFC3339), cs.RemoteAddr, cs.OS, cs.Arch, cs.Version
|
||||
}
|
||||
return false, "", "", "", ""
|
||||
return false, "", "", "", "", ""
|
||||
}
|
||||
|
||||
// GetClientPluginStatus 获取客户端插件运行状态
|
||||
@@ -633,6 +635,7 @@ func (s *Server) GetAllClientStatus() map[string]struct {
|
||||
RemoteAddr string
|
||||
OS string
|
||||
Arch string
|
||||
Version string
|
||||
} {
|
||||
// 先复制客户端引用,避免嵌套锁
|
||||
s.mu.RLock()
|
||||
@@ -648,6 +651,7 @@ func (s *Server) GetAllClientStatus() map[string]struct {
|
||||
RemoteAddr string
|
||||
OS string
|
||||
Arch string
|
||||
Version string
|
||||
})
|
||||
|
||||
for _, cs := range clients {
|
||||
@@ -658,12 +662,14 @@ func (s *Server) GetAllClientStatus() map[string]struct {
|
||||
RemoteAddr string
|
||||
OS string
|
||||
Arch string
|
||||
Version string
|
||||
}{
|
||||
Online: true,
|
||||
LastPing: cs.LastPing.Format(time.RFC3339),
|
||||
RemoteAddr: cs.RemoteAddr,
|
||||
OS: cs.OS,
|
||||
Arch: cs.Arch,
|
||||
Version: cs.Version,
|
||||
}
|
||||
cs.mu.Unlock()
|
||||
}
|
||||
@@ -1869,3 +1875,46 @@ func (s *Server) StopClientLogStream(sessionID string) {
|
||||
func boolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
// GetClientSystemStats 获取客户端系统状态
|
||||
func (s *Server) GetClientSystemStats(clientID string) (*protocol.SystemStatsResponse, 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()
|
||||
|
||||
// 设置超时
|
||||
stream.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
|
||||
// 发送请求
|
||||
msg, _ := protocol.NewMessage(protocol.MsgTypeSystemStatsRequest, nil)
|
||||
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.MsgTypeSystemStatsResponse {
|
||||
return nil, fmt.Errorf("unexpected response type: %d", resp.Type)
|
||||
}
|
||||
|
||||
var stats protocol.SystemStatsResponse
|
||||
if err := resp.ParsePayload(&stats); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user