From b56cfb7b1e4693d81305744db0f79f9921484be7 Mon Sep 17 00:00:00 2001 From: kevin Date: Fri, 19 Jun 2026 20:03:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=88=A0=E9=99=A4=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/store/store_query.go | 47 +++++++++++++++++++ internal/web/web.go | 17 +++++++ meshmap_frontend/src/App.vue | 31 +++++++++++- meshmap_frontend/src/api.ts | 5 ++ meshmap_frontend/src/components/MeshMap.vue | 9 ++++ .../src/components/NodeListPanel.vue | 9 ++++ 6 files changed, 117 insertions(+), 1 deletion(-) diff --git a/internal/store/store_query.go b/internal/store/store_query.go index 4f9e3ec..70412cd 100644 --- a/internal/store/store_query.go +++ b/internal/store/store_query.go @@ -232,6 +232,53 @@ func (s *Store) DeleteNode(nodeID string) error { }) } +// PurgeNode 在「删除节点」菜单触发时执行:除了 nodeinfo + map_report, +// 还要把 text_message(频道聊天)以及 position/telemetry/routing/traceroute +// 这些以 from_id 关联的数据包记录一起清理。 +// +// 任一表删到记录就视为成功;全部为空才返回 ErrRecordNotFound。 +func (s *Store) PurgeNode(nodeID string) error { + if nodeID == "" { + return gorm.ErrRecordNotFound + } + return s.db.Transaction(func(tx *gorm.DB) error { + var totalAffected int64 + + nodeResult := tx.Where("node_id = ?", nodeID).Delete(&NodeInfoRecord{}) + if nodeResult.Error != nil { + return nodeResult.Error + } + totalAffected += nodeResult.RowsAffected + + reportResult := tx.Where("node_id = ?", nodeID).Delete(&MapReportRecord{}) + if reportResult.Error != nil { + return reportResult.Error + } + totalAffected += reportResult.RowsAffected + + // 以 from_id 关联:聊天消息 + 数据包流水 + fromIDTargets := []any{ + &TextMessageRecord{}, + &PositionRecord{}, + &TelemetryRecord{}, + &RoutingRecord{}, + &TracerouteRecord{}, + } + for _, model := range fromIDTargets { + res := tx.Where("from_id = ?", nodeID).Delete(model) + if res.Error != nil { + return res.Error + } + totalAffected += res.RowsAffected + } + + if totalAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil + }) +} + func applyNodeFilters(q *gorm.DB, opts ListOptions) *gorm.DB { if opts.NodeID != "" { q = q.Where("node_id = ?", opts.NodeID) diff --git a/internal/web/web.go b/internal/web/web.go index deb4ac3..eaf9013 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -338,6 +338,23 @@ func registerAdminRoutes(r gin.IRouter, store *storepkg.Store, sessions *auth.Ma } c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) + // purge:除了 nodeinfo + map_report,还把 text_message 与 position/telemetry/routing/traceroute + // 中按 from_id 关联到该节点的记录一并删除。 + protected.DELETE("/nodes/:id/purge", func(c *gin.Context) { + nodeID := c.Param("id") + if nodeID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid node id"}) + return + } + if err := store.PurgeNode(nodeID); errors.Is(err, gorm.ErrRecordNotFound) { + c.JSON(http.StatusNotFound, gin.H{"error": "node not found"}) + return + } else if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"status": "ok"}) + }) } func registerNodeInfoRoutes(r gin.IRouter, store *storepkg.Store, path string) { diff --git a/meshmap_frontend/src/App.vue b/meshmap_frontend/src/App.vue index cf4e751..1cd48d0 100644 --- a/meshmap_frontend/src/App.vue +++ b/meshmap_frontend/src/App.vue @@ -1,6 +1,6 @@