fix: resolve merge conflicts and fix monitoring bugs

- 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
This commit is contained in:
sagitchu
2026-03-18 14:12:47 +08:00
parent 455900ba41
commit 5972378897
7 changed files with 57 additions and 117 deletions
@@ -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 == "" {
@@ -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 {
@@ -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 {
+13 -3
View File
@@ -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
+10
View File
@@ -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
+1
View File
@@ -402,6 +402,7 @@ export interface NodeMetricApiItem {
load15: number;
tcpConns: number;
udpConns: number;
uptime: number;
}
export interface TunnelMetricApiItem {
+32 -32
View File
@@ -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(() => {