From 597237889754a1380fc4fe6c1a340279f153aa0d Mon Sep 17 00:00:00 2001 From: sagitchu Date: Wed, 18 Mar 2026 14:12:47 +0800 Subject: [PATCH] fix: resolve merge conflicts and fix monitoring bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add missing 'uptime' field to NodeMetricApiItem type definition - Fix WS message handling: non-UpgradeProgress typed messages now broadcast via broadcastInfo instead of being silently dropped - Strengthen looksLikeSystemInfoMessage heuristic to require ≥3 matching keys to avoid false positives - Fix tab/space indentation inconsistency in admin.tsx useEffect - Remove duplicate method declarations from merge (repository_control, mutations) - Update tunnel_entry_sqlite_test to use renamed Tx suffix function --- go-backend/internal/http/handler/mutations.go | 14 ---- .../http/handler/tunnel_entry_sqlite_test.go | 2 +- .../internal/store/repo/repository_control.go | 67 ------------------- go-backend/internal/ws/server.go | 16 ++++- plans/045-release-2-1-8.md | 10 +++ vite-frontend/src/api/types.ts | 1 + vite-frontend/src/layouts/admin.tsx | 64 +++++++++--------- 7 files changed, 57 insertions(+), 117 deletions(-) create mode 100644 plans/045-release-2-1-8.md diff --git a/go-backend/internal/http/handler/mutations.go b/go-backend/internal/http/handler/mutations.go index 8dbde89..fa6131e 100644 --- a/go-backend/internal/http/handler/mutations.go +++ b/go-backend/internal/http/handler/mutations.go @@ -4165,20 +4165,6 @@ func (h *Handler) validateForwardPortAvailability(node *nodeRecord, port int, cu return nil } -func (h *Handler) validateForwardPortAvailabilityTx(tx *gorm.DB, node *nodeRecord, port int, currentForwardID int64) error { - if h == nil || h.repo == nil || tx == nil || node == nil || port <= 0 { - return nil - } - occupied, err := h.repo.HasOtherForwardOnNodePortTx(tx, node.ID, port, currentForwardID) - if err != nil { - return err - } - if occupied { - return fmt.Errorf("节点 %s 端口 %d 已被其他转发占用", node.Name, port) - } - return nil -} - func parsePortRangeMinMax(input string) (int, int) { input = strings.TrimSpace(input) if input == "" { diff --git a/go-backend/internal/http/handler/tunnel_entry_sqlite_test.go b/go-backend/internal/http/handler/tunnel_entry_sqlite_test.go index 67f4793..f6aa074 100644 --- a/go-backend/internal/http/handler/tunnel_entry_sqlite_test.go +++ b/go-backend/internal/http/handler/tunnel_entry_sqlite_test.go @@ -83,7 +83,7 @@ func TestValidateTunnelEntryPortConflictsForNewEntriesDoesNotBlockOnSQLiteTx(t * doneCh := make(chan struct{}) go func() { defer close(doneCh) - errCh <- h.validateTunnelEntryPortConflictsForNewEntries(tx, tunnelID, []int64{oldEntryID}, []int64{oldEntryID, newEntryID}) + errCh <- h.validateTunnelEntryPortConflictsForNewEntriesTx(tx, tunnelID, []int64{oldEntryID}, []int64{oldEntryID, newEntryID}) }() select { diff --git a/go-backend/internal/store/repo/repository_control.go b/go-backend/internal/store/repo/repository_control.go index 7198d62..e1c0882 100644 --- a/go-backend/internal/store/repo/repository_control.go +++ b/go-backend/internal/store/repo/repository_control.go @@ -64,36 +64,6 @@ func (r *Repository) ListForwardsByTunnelTx(tx *gorm.DB, tunnelID int64) ([]mode return rows, nil } -func (r *Repository) ListForwardsByTunnelTx(tx *gorm.DB, tunnelID int64) ([]model.ForwardRecord, error) { - if tx == nil { - return nil, errors.New("database unavailable") - } - var forwards []model.Forward - err := tx.Where("tunnel_id = ?", tunnelID).Order("id ASC").Find(&forwards).Error - if err != nil { - return nil, err - } - rows := make([]model.ForwardRecord, 0, len(forwards)) - for _, f := range forwards { - rows = append(rows, model.ForwardRecord{ - ID: f.ID, - UserID: f.UserID, - UserName: f.UserName, - Name: f.Name, - TunnelID: f.TunnelID, - RemoteAddr: f.RemoteAddr, - Strategy: f.Strategy, - Status: f.Status, - SpeedID: f.SpeedID, - }) - } - for i := range rows { - if strings.TrimSpace(rows[i].Strategy) == "" { - rows[i].Strategy = "fifo" - } - } - return rows, nil -} func (r *Repository) ListActiveTunnelIDsByNode(nodeID int64) ([]int64, error) { if r == nil || r.db == nil { @@ -156,25 +126,6 @@ func (r *Repository) ListForwardPortsTx(tx *gorm.DB, forwardID int64) ([]model.F return rows, nil } -func (r *Repository) ListForwardPortsTx(tx *gorm.DB, forwardID int64) ([]model.ForwardPortRecord, error) { - if tx == nil { - return nil, errors.New("database unavailable") - } - var ports []model.ForwardPort - err := tx.Where("forward_id = ?", forwardID).Order("id ASC").Find(&ports).Error - if err != nil { - return nil, err - } - rows := make([]model.ForwardPortRecord, 0, len(ports)) - for _, p := range ports { - inIP := "" - if p.InIP.Valid { - inIP = p.InIP.String - } - rows = append(rows, model.ForwardPortRecord{NodeID: p.NodeID, Port: p.Port, InIP: inIP}) - } - return rows, nil -} func (r *Repository) HasOtherForwardOnNodePort(nodeID int64, port int, currentForwardID int64) (bool, error) { if r == nil || r.db == nil { @@ -202,24 +153,6 @@ func (r *Repository) HasOtherForwardOnNodePortTx(tx *gorm.DB, nodeID int64, port return count > 0, nil } -func (r *Repository) HasOtherForwardOnNodePortTx(tx *gorm.DB, nodeID int64, port int, currentForwardID int64) (bool, error) { - if tx == nil { - return false, errors.New("database unavailable") - } - if nodeID <= 0 || port <= 0 { - return false, nil - } - - var count int64 - err := tx.Model(&model.ForwardPort{}). - Where("node_id = ? AND port = ? AND forward_id <> ?", nodeID, port, currentForwardID). - Count(&count).Error - if err != nil { - return false, err - } - - return count > 0, nil -} func (r *Repository) GetTunnelOutProtocol(tunnelID int64) (string, error) { if r == nil || r.db == nil { diff --git a/go-backend/internal/ws/server.go b/go-backend/internal/ws/server.go index d92e400..1e24116 100644 --- a/go-backend/internal/ws/server.go +++ b/go-backend/internal/ws/server.go @@ -258,10 +258,16 @@ func (s *Server) handleNode(w http.ResponseWriter, r *http.Request, nodeID int64 Type string `json:"type"` } if json.Unmarshal([]byte(msg), &parsed) == nil && parsed.Type != "" { - if parsed.Type == "UpgradeProgress" { + switch parsed.Type { + case "UpgradeProgress": s.broadcastTyped(nodeID, "upgrade_progress", msg) + continue + default: + // Unknown typed messages still get broadcast so future + // agent message types are not silently lost. + s.broadcastInfo(nodeID, msg) + continue } - continue } if looksLikeSystemInfoMessage(msg) { @@ -308,9 +314,13 @@ func looksLikeSystemInfoMessage(msg string) bool { "\"load5\"", "\"load15\"", } + matched := 0 for _, k := range keys { if strings.Contains(msg, k) { - return true + matched++ + if matched >= 3 { + return true + } } } return false diff --git a/plans/045-release-2-1-8.md b/plans/045-release-2-1-8.md new file mode 100644 index 0000000..9e327ec --- /dev/null +++ b/plans/045-release-2-1-8.md @@ -0,0 +1,10 @@ +# PLAN: Release 2.1.8 + +## Overview +Synchronize with the main branch and release a new tag `2.1.8`. + +## Tasks +- [x] Pull latest changes from `main` +- [x] Update `AGENTS.md` with new tag and current commit hash (PR #337 created and set to auto-merge) +- [x] Create git tag `2.1.8` +- [x] Push git tag `2.1.8` to origin diff --git a/vite-frontend/src/api/types.ts b/vite-frontend/src/api/types.ts index 11ae18e..d40134a 100644 --- a/vite-frontend/src/api/types.ts +++ b/vite-frontend/src/api/types.ts @@ -402,6 +402,7 @@ export interface NodeMetricApiItem { load15: number; tcpConns: number; udpConns: number; + uptime: number; } export interface TunnelMetricApiItem { diff --git a/vite-frontend/src/layouts/admin.tsx b/vite-frontend/src/layouts/admin.tsx index 481d57f..a1701ff 100644 --- a/vite-frontend/src/layouts/admin.tsx +++ b/vite-frontend/src/layouts/admin.tsx @@ -202,40 +202,40 @@ export default function AdminLayout({ setUsername(name); setIsAdmin(adminFlag); - // Monitor permission is not strictly role-based; non-admin users may be - // granted access explicitly. Fetch a lightweight capability flag so we can - // avoid a confusing 403 navigation. - if (adminFlag) { - setMonitorAllowed(true); - setMonitorAccessReason(null); - return; - } + // Monitor permission is not strictly role-based; non-admin users may be + // granted access explicitly. Fetch a lightweight capability flag so we can + // avoid a confusing 403 navigation. + if (adminFlag) { + setMonitorAllowed(true); + setMonitorAccessReason(null); + return; + } - let cancelled = false; - (async () => { - try { - const res = await getMonitorAccess(); - if (cancelled) return; - if (res.code === 0 && res.data) { - setMonitorAllowed(Boolean(res.data.allowed)); - setMonitorAccessReason( - res.data.allowed ? null : (res.data.reason || null), - ); - return; - } - // Fail open to preserve legacy navigation behavior. - setMonitorAllowed(true); - setMonitorAccessReason(null); - } catch { - if (cancelled) return; - setMonitorAllowed(true); - setMonitorAccessReason(null); - } - })(); + let cancelled = false; + (async () => { + try { + const res = await getMonitorAccess(); + if (cancelled) return; + if (res.code === 0 && res.data) { + setMonitorAllowed(Boolean(res.data.allowed)); + setMonitorAccessReason( + res.data.allowed ? null : (res.data.reason || null), + ); + return; + } + // Fail open to preserve legacy navigation behavior. + setMonitorAllowed(true); + setMonitorAccessReason(null); + } catch { + if (cancelled) return; + setMonitorAllowed(true); + setMonitorAccessReason(null); + } + })(); - return () => { - cancelled = true; - }; + return () => { + cancelled = true; + }; }, []); useEffect(() => {