From cfb6cae78abbf2b8b89d756c75c02fc382aea684 Mon Sep 17 00:00:00 2001 From: 0xJacky Date: Sun, 9 Nov 2025 09:41:33 +0000 Subject: [PATCH] refactor: add config to disable site health check #1427, #1415, #1413 --- api/nginx/websocket.go | 110 +++++++--- api/sites/router.go | 2 +- api/sites/sitecheck.go | 61 ++---- api/sites/websocket.go | 1 - app/src/api/site_navigation.ts | 4 +- app/src/components/PageHeader/PageHeader.vue | 4 - app/src/views/dashboard/SiteNavigation.vue | 189 +++++------------- .../views/dashboard/components/SiteCard.vue | 6 +- .../components/SiteHealthCheckModal.vue | 30 +-- .../components/SiteNavigationToolbar.vue | 13 +- go.mod | 32 +-- go.sum | 35 ++++ internal/sitecheck/checker.go | 69 +++---- internal/sitecheck/enhanced_checker.go | 92 ++------- internal/sitecheck/ordering.go | 6 +- internal/sitecheck/types.go | 30 ++- model/site_config.go | 26 +-- 17 files changed, 304 insertions(+), 406 deletions(-) diff --git a/api/nginx/websocket.go b/api/nginx/websocket.go index 930cd5e9..c48ec234 100644 --- a/api/nginx/websocket.go +++ b/api/nginx/websocket.go @@ -21,6 +21,36 @@ type PerformanceClient struct { ctx context.Context cancel context.CancelFunc mutex sync.RWMutex + closed bool +} + +func (c *PerformanceClient) trySend(message interface{}) bool { + c.mutex.RLock() + if c.closed { + c.mutex.RUnlock() + return false + } + + select { + case c.send <- message: + c.mutex.RUnlock() + return true + default: + c.mutex.RUnlock() + return false + } +} + +func (c *PerformanceClient) closeSendChannel() { + c.mutex.Lock() + if c.closed { + c.mutex.Unlock() + return + } + + close(c.send) + c.closed = true + c.mutex.Unlock() } // PerformanceHub manages WebSocket connections for Nginx performance monitoring @@ -60,20 +90,18 @@ func (h *PerformanceHub) run() { case client := <-h.register: h.mutex.Lock() h.clients[client] = true + currentClients := len(h.clients) h.mutex.Unlock() - logger.Debug("Nginx performance client connected, total clients:", len(h.clients)) + logger.Debug("Nginx performance client connected, total clients:", currentClients) // Send initial data to the new client go h.sendPerformanceDataToClient(client) case client := <-h.unregister: - h.mutex.Lock() - if _, ok := h.clients[client]; ok { - delete(h.clients, client) - close(client.send) + currentClients, removed := h.removeClient(client) + if removed { + logger.Debug("Nginx performance client disconnected, total clients:", currentClients) } - h.mutex.Unlock() - logger.Debug("Nginx performance client disconnected, total clients:", len(h.clients)) case <-h.ticker.C: // Send performance data to all connected clients @@ -82,24 +110,55 @@ func (h *PerformanceHub) run() { case <-kernel.Context.Done(): logger.Debug("PerformanceHub: Context cancelled, closing WebSocket") // Shutdown all clients - h.mutex.Lock() - for client := range h.clients { - close(client.send) - delete(h.clients, client) + for _, client := range h.activeClients() { + h.removeClient(client) } - h.mutex.Unlock() return } } } +func (h *PerformanceHub) activeClients() []*PerformanceClient { + h.mutex.RLock() + if len(h.clients) == 0 { + h.mutex.RUnlock() + return nil + } + + clients := make([]*PerformanceClient, 0, len(h.clients)) + for client := range h.clients { + clients = append(clients, client) + } + h.mutex.RUnlock() + return clients +} + +func (h *PerformanceHub) removeClient(client *PerformanceClient) (remaining int, removed bool) { + h.mutex.Lock() + _, removed = h.clients[client] + if removed { + delete(h.clients, client) + } + remaining = len(h.clients) + h.mutex.Unlock() + + if removed { + client.closeSendChannel() + } + return remaining, removed +} + // sendPerformanceDataToClient sends performance data to a specific client func (h *PerformanceHub) sendPerformanceDataToClient(client *PerformanceClient) { + select { + case <-client.ctx.Done(): + return + default: + } + response := performance.GetPerformanceData() - select { - case client.send <- response: - default: + if !client.trySend(response) { // Channel is full, remove client h.unregister <- client } @@ -107,27 +166,20 @@ func (h *PerformanceHub) sendPerformanceDataToClient(client *PerformanceClient) // broadcastPerformanceData sends performance data to all connected clients func (h *PerformanceHub) broadcastPerformanceData() { - h.mutex.RLock() - - // Check if there are any connected clients - if len(h.clients) == 0 { - h.mutex.RUnlock() + clients := h.activeClients() + if len(clients) == 0 { return } - // Only get performance data if there are connected clients response := performance.GetPerformanceData() - for client := range h.clients { - select { - case client.send <- response: - default: - // Channel is full, remove client - close(client.send) - delete(h.clients, client) + for _, client := range clients { + if client.trySend(response) { + continue } + + h.removeClient(client) } - h.mutex.RUnlock() } // WebSocket upgrader configuration diff --git a/api/sites/router.go b/api/sites/router.go index 372c32a5..8254f45c 100644 --- a/api/sites/router.go +++ b/api/sites/router.go @@ -18,7 +18,7 @@ func InitRouter(r *gin.RouterGroup) { r.GET("site_navigation/status", GetSiteNavigationStatus) r.POST("site_navigation/order", UpdateSiteOrder) r.GET("site_navigation/health_check/:id", GetHealthCheck) - r.PUT("site_navigation/health_check/:id", UpdateHealthCheck) + r.POST("site_navigation/health_check/:id", UpdateHealthCheck) r.POST("site_navigation/test_health_check/:id", TestHealthCheck) r.GET("site_navigation_ws", SiteNavigationWebSocket) diff --git a/api/sites/sitecheck.go b/api/sites/sitecheck.go index a6110fd5..74080003 100644 --- a/api/sites/sitecheck.go +++ b/api/sites/sitecheck.go @@ -12,6 +12,7 @@ import ( "github.com/spf13/cast" "github.com/uozi-tech/cosy" "github.com/uozi-tech/cosy/logger" + "gorm.io/gorm/clause" ) // GetSiteNavigation returns all sites for navigation dashboard @@ -54,16 +55,26 @@ func UpdateSiteOrder(c *gin.Context) { } // updateSiteOrderBatchByIds updates site order in batch using IDs +// Uses INSERT INTO ... ON DUPLICATE KEY UPDATE for better performance func updateSiteOrderBatchByIds(orderedIds []uint64) error { - sc := query.SiteConfig - - for i, id := range orderedIds { - if _, err := sc.Where(sc.ID.Eq(id)).Update(sc.CustomOrder, i); err != nil { - return err - } + if len(orderedIds) == 0 { + return nil } - return nil + sc := query.SiteConfig + + records := make([]*model.SiteConfig, 0, len(orderedIds)) + for i, id := range orderedIds { + records = append(records, &model.SiteConfig{ + Model: model.Model{ID: id}, + CustomOrder: i, + }) + } + + return sc.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "id"}}, + DoUpdates: clause.AssignmentColumns([]string{"custom_order"}), + }).Create(records...) } // GetHealthCheck gets health check configuration for a site @@ -102,41 +113,7 @@ func ensureHealthCheckConfig(siteConfig *model.SiteConfig) { // UpdateHealthCheck updates health check configuration for a site func UpdateHealthCheck(c *gin.Context) { - id := cast.ToUint64(c.Param("id")) - - var req model.SiteConfig - - if !cosy.BindAndValid(c, &req) { - return - } - - sc := query.SiteConfig - siteConfig, err := sc.Where(sc.ID.Eq(id)).First() - if err != nil { - cosy.ErrHandler(c, err) - return - } - - siteConfig.HealthCheckEnabled = req.HealthCheckEnabled - siteConfig.CheckInterval = req.CheckInterval - siteConfig.Timeout = req.Timeout - siteConfig.UserAgent = req.UserAgent - siteConfig.MaxRedirects = req.MaxRedirects - siteConfig.FollowRedirects = req.FollowRedirects - siteConfig.CheckFavicon = req.CheckFavicon - - if req.HealthCheckConfig != nil { - siteConfig.HealthCheckConfig = req.HealthCheckConfig - } - - if err = query.SiteConfig.Save(siteConfig); err != nil { - cosy.ErrHandler(c, err) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "Health check configuration updated successfully", - }) + cosy.Core[model.SiteConfig](c).Modify() } // TestHealthCheck tests a health check configuration without saving it diff --git a/api/sites/websocket.go b/api/sites/websocket.go index 68c1ee32..f43cd2d3 100644 --- a/api/sites/websocket.go +++ b/api/sites/websocket.go @@ -74,7 +74,6 @@ func (wm *WSManager) BroadcastUpdate(sites []*sitecheck.SiteInfo) { for conn := range wm.connections { go func(c *websocket.Conn) { if err := sendSiteData(c, MessageTypeUpdate, sites); err != nil { - logger.Error("Failed to send broadcast update:", err) wm.RemoveConnection(c) c.Close() } diff --git a/app/src/api/site_navigation.ts b/app/src/api/site_navigation.ts index ae8c8211..d2245c25 100644 --- a/app/src/api/site_navigation.ts +++ b/app/src/api/site_navigation.ts @@ -3,6 +3,7 @@ import { http } from '@uozi-admin/request' export interface SiteInfo { id: number // primary identifier for API operations + health_check_enabled: boolean // whether health check is enabled host: string // host:port format port: number scheme: string // http, https, grpc, grpcs @@ -23,6 +24,7 @@ export interface SiteInfo { } export interface HealthCheckConfig { + health_check_enabled?: boolean check_interval?: number timeout?: number user_agent?: string @@ -135,7 +137,7 @@ export const siteNavigationApi = { // Update health check configuration updateHealthCheck(id: number, config: HealthCheckConfig): Promise<{ message: string }> { - return http.put(`/site_navigation/health_check/${id}`, config) + return http.post(`/site_navigation/health_check/${id}`, config) }, // Test health check configuration diff --git a/app/src/components/PageHeader/PageHeader.vue b/app/src/components/PageHeader/PageHeader.vue index 53bacf0d..5a57cfda 100644 --- a/app/src/components/PageHeader/PageHeader.vue +++ b/app/src/components/PageHeader/PageHeader.vue @@ -56,10 +56,6 @@ const name = computed(() => { margin-bottom: 16px; } - @media (max-height: 800px) { - display: none; - } - .detail { display: flex; /*margin-bottom: 16px;*/ diff --git a/app/src/views/dashboard/SiteNavigation.vue b/app/src/views/dashboard/SiteNavigation.vue index 886b067d..3381f3e8 100644 --- a/app/src/views/dashboard/SiteNavigation.vue +++ b/app/src/views/dashboard/SiteNavigation.vue @@ -1,8 +1,9 @@