Skip to content

Commit cfb6cae

Browse files
committed
refactor: add config to disable site health check #1427, #1415, #1413
1 parent d24d845 commit cfb6cae

File tree

17 files changed

+300
-402
lines changed

17 files changed

+300
-402
lines changed

api/nginx/websocket.go

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,36 @@ type PerformanceClient struct {
2121
ctx context.Context
2222
cancel context.CancelFunc
2323
mutex sync.RWMutex
24+
closed bool
25+
}
26+
27+
func (c *PerformanceClient) trySend(message interface{}) bool {
28+
c.mutex.RLock()
29+
if c.closed {
30+
c.mutex.RUnlock()
31+
return false
32+
}
33+
34+
select {
35+
case c.send <- message:
36+
c.mutex.RUnlock()
37+
return true
38+
default:
39+
c.mutex.RUnlock()
40+
return false
41+
}
42+
}
43+
44+
func (c *PerformanceClient) closeSendChannel() {
45+
c.mutex.Lock()
46+
if c.closed {
47+
c.mutex.Unlock()
48+
return
49+
}
50+
51+
close(c.send)
52+
c.closed = true
53+
c.mutex.Unlock()
2454
}
2555

2656
// PerformanceHub manages WebSocket connections for Nginx performance monitoring
@@ -60,20 +90,18 @@ func (h *PerformanceHub) run() {
6090
case client := <-h.register:
6191
h.mutex.Lock()
6292
h.clients[client] = true
93+
currentClients := len(h.clients)
6394
h.mutex.Unlock()
64-
logger.Debug("Nginx performance client connected, total clients:", len(h.clients))
95+
logger.Debug("Nginx performance client connected, total clients:", currentClients)
6596

6697
// Send initial data to the new client
6798
go h.sendPerformanceDataToClient(client)
6899

69100
case client := <-h.unregister:
70-
h.mutex.Lock()
71-
if _, ok := h.clients[client]; ok {
72-
delete(h.clients, client)
73-
close(client.send)
101+
currentClients, removed := h.removeClient(client)
102+
if removed {
103+
logger.Debug("Nginx performance client disconnected, total clients:", currentClients)
74104
}
75-
h.mutex.Unlock()
76-
logger.Debug("Nginx performance client disconnected, total clients:", len(h.clients))
77105

78106
case <-h.ticker.C:
79107
// Send performance data to all connected clients
@@ -82,52 +110,76 @@ func (h *PerformanceHub) run() {
82110
case <-kernel.Context.Done():
83111
logger.Debug("PerformanceHub: Context cancelled, closing WebSocket")
84112
// Shutdown all clients
85-
h.mutex.Lock()
86-
for client := range h.clients {
87-
close(client.send)
88-
delete(h.clients, client)
113+
for _, client := range h.activeClients() {
114+
h.removeClient(client)
89115
}
90-
h.mutex.Unlock()
91116
return
92117
}
93118
}
94119
}
95120

121+
func (h *PerformanceHub) activeClients() []*PerformanceClient {
122+
h.mutex.RLock()
123+
if len(h.clients) == 0 {
124+
h.mutex.RUnlock()
125+
return nil
126+
}
127+
128+
clients := make([]*PerformanceClient, 0, len(h.clients))
129+
for client := range h.clients {
130+
clients = append(clients, client)
131+
}
132+
h.mutex.RUnlock()
133+
return clients
134+
}
135+
136+
func (h *PerformanceHub) removeClient(client *PerformanceClient) (remaining int, removed bool) {
137+
h.mutex.Lock()
138+
_, removed = h.clients[client]
139+
if removed {
140+
delete(h.clients, client)
141+
}
142+
remaining = len(h.clients)
143+
h.mutex.Unlock()
144+
145+
if removed {
146+
client.closeSendChannel()
147+
}
148+
return remaining, removed
149+
}
150+
96151
// sendPerformanceDataToClient sends performance data to a specific client
97152
func (h *PerformanceHub) sendPerformanceDataToClient(client *PerformanceClient) {
98-
response := performance.GetPerformanceData()
99-
100153
select {
101-
case client.send <- response:
154+
case <-client.ctx.Done():
155+
return
102156
default:
157+
}
158+
159+
response := performance.GetPerformanceData()
160+
161+
if !client.trySend(response) {
103162
// Channel is full, remove client
104163
h.unregister <- client
105164
}
106165
}
107166

108167
// broadcastPerformanceData sends performance data to all connected clients
109168
func (h *PerformanceHub) broadcastPerformanceData() {
110-
h.mutex.RLock()
111-
112-
// Check if there are any connected clients
113-
if len(h.clients) == 0 {
114-
h.mutex.RUnlock()
169+
clients := h.activeClients()
170+
if len(clients) == 0 {
115171
return
116172
}
117173

118-
// Only get performance data if there are connected clients
119174
response := performance.GetPerformanceData()
120175

121-
for client := range h.clients {
122-
select {
123-
case client.send <- response:
124-
default:
125-
// Channel is full, remove client
126-
close(client.send)
127-
delete(h.clients, client)
176+
for _, client := range clients {
177+
if client.trySend(response) {
178+
continue
128179
}
180+
181+
h.removeClient(client)
129182
}
130-
h.mutex.RUnlock()
131183
}
132184

133185
// WebSocket upgrader configuration

api/sites/router.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func InitRouter(r *gin.RouterGroup) {
1818
r.GET("site_navigation/status", GetSiteNavigationStatus)
1919
r.POST("site_navigation/order", UpdateSiteOrder)
2020
r.GET("site_navigation/health_check/:id", GetHealthCheck)
21-
r.PUT("site_navigation/health_check/:id", UpdateHealthCheck)
21+
r.POST("site_navigation/health_check/:id", UpdateHealthCheck)
2222
r.POST("site_navigation/test_health_check/:id", TestHealthCheck)
2323
r.GET("site_navigation_ws", SiteNavigationWebSocket)
2424

api/sites/sitecheck.go

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/spf13/cast"
1313
"github.com/uozi-tech/cosy"
1414
"github.com/uozi-tech/cosy/logger"
15+
"gorm.io/gorm/clause"
1516
)
1617

1718
// GetSiteNavigation returns all sites for navigation dashboard
@@ -54,16 +55,26 @@ func UpdateSiteOrder(c *gin.Context) {
5455
}
5556

5657
// updateSiteOrderBatchByIds updates site order in batch using IDs
58+
// Uses INSERT INTO ... ON DUPLICATE KEY UPDATE for better performance
5759
func updateSiteOrderBatchByIds(orderedIds []uint64) error {
60+
if len(orderedIds) == 0 {
61+
return nil
62+
}
63+
5864
sc := query.SiteConfig
5965

66+
records := make([]*model.SiteConfig, 0, len(orderedIds))
6067
for i, id := range orderedIds {
61-
if _, err := sc.Where(sc.ID.Eq(id)).Update(sc.CustomOrder, i); err != nil {
62-
return err
63-
}
68+
records = append(records, &model.SiteConfig{
69+
Model: model.Model{ID: id},
70+
CustomOrder: i,
71+
})
6472
}
6573

66-
return nil
74+
return sc.Clauses(clause.OnConflict{
75+
Columns: []clause.Column{{Name: "id"}},
76+
DoUpdates: clause.AssignmentColumns([]string{"custom_order"}),
77+
}).Create(records...)
6778
}
6879

6980
// GetHealthCheck gets health check configuration for a site
@@ -102,41 +113,7 @@ func ensureHealthCheckConfig(siteConfig *model.SiteConfig) {
102113

103114
// UpdateHealthCheck updates health check configuration for a site
104115
func UpdateHealthCheck(c *gin.Context) {
105-
id := cast.ToUint64(c.Param("id"))
106-
107-
var req model.SiteConfig
108-
109-
if !cosy.BindAndValid(c, &req) {
110-
return
111-
}
112-
113-
sc := query.SiteConfig
114-
siteConfig, err := sc.Where(sc.ID.Eq(id)).First()
115-
if err != nil {
116-
cosy.ErrHandler(c, err)
117-
return
118-
}
119-
120-
siteConfig.HealthCheckEnabled = req.HealthCheckEnabled
121-
siteConfig.CheckInterval = req.CheckInterval
122-
siteConfig.Timeout = req.Timeout
123-
siteConfig.UserAgent = req.UserAgent
124-
siteConfig.MaxRedirects = req.MaxRedirects
125-
siteConfig.FollowRedirects = req.FollowRedirects
126-
siteConfig.CheckFavicon = req.CheckFavicon
127-
128-
if req.HealthCheckConfig != nil {
129-
siteConfig.HealthCheckConfig = req.HealthCheckConfig
130-
}
131-
132-
if err = query.SiteConfig.Save(siteConfig); err != nil {
133-
cosy.ErrHandler(c, err)
134-
return
135-
}
136-
137-
c.JSON(http.StatusOK, gin.H{
138-
"message": "Health check configuration updated successfully",
139-
})
116+
cosy.Core[model.SiteConfig](c).Modify()
140117
}
141118

142119
// TestHealthCheck tests a health check configuration without saving it

api/sites/websocket.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ func (wm *WSManager) BroadcastUpdate(sites []*sitecheck.SiteInfo) {
7474
for conn := range wm.connections {
7575
go func(c *websocket.Conn) {
7676
if err := sendSiteData(c, MessageTypeUpdate, sites); err != nil {
77-
logger.Error("Failed to send broadcast update:", err)
7877
wm.RemoveConnection(c)
7978
c.Close()
8079
}

app/src/api/site_navigation.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { http } from '@uozi-admin/request'
33

44
export interface SiteInfo {
55
id: number // primary identifier for API operations
6+
health_check_enabled: boolean // whether health check is enabled
67
host: string // host:port format
78
port: number
89
scheme: string // http, https, grpc, grpcs
@@ -23,6 +24,7 @@ export interface SiteInfo {
2324
}
2425

2526
export interface HealthCheckConfig {
27+
health_check_enabled?: boolean
2628
check_interval?: number
2729
timeout?: number
2830
user_agent?: string
@@ -135,7 +137,7 @@ export const siteNavigationApi = {
135137

136138
// Update health check configuration
137139
updateHealthCheck(id: number, config: HealthCheckConfig): Promise<{ message: string }> {
138-
return http.put(`/site_navigation/health_check/${id}`, config)
140+
return http.post(`/site_navigation/health_check/${id}`, config)
139141
},
140142

141143
// Test health check configuration

app/src/components/PageHeader/PageHeader.vue

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,6 @@ const name = computed(() => {
5656
margin-bottom: 16px;
5757
}
5858
59-
@media (max-height: 800px) {
60-
display: none;
61-
}
62-
6359
.detail {
6460
display: flex;
6561
/*margin-bottom: 16px;*/

0 commit comments

Comments
 (0)