Skip to content

Commit eac12c7

Browse files
committed
update: 服务管理详情页布局部分功能实现
1 parent 1c6e1f1 commit eac12c7

File tree

22 files changed

+3100
-913
lines changed

22 files changed

+3100
-913
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<img src="docs/nodepassdash-logo.svg" alt="NodePassDash" height="80">
33
</div>
44

5-
![Version](https://img.shields.io/badge/version-3.2.0--beta2-blue.svg)
5+
![Version](https://img.shields.io/badge/version-3.2.0--beta3-blue.svg)
66
![GitHub license](https://img.shields.io/github/license/NodePassProject/NodePassDash)
77

88
NodePassDash是一个现代化的 NodePass 管理界面,基于 Go 后端 + React + Vite、HeroUI 和 TypeScript 构建。提供实时隧道监控、流量统计和端点管理功能。

internal/api/services.go

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,18 @@ func SetupServicesRoutes(rg *gin.RouterGroup, servicesService *services.ServiceI
2424

2525
// 服务相关路由
2626
rg.GET("/services", servicesHandler.GetServices)
27-
rg.GET("/services/:sid/:type", servicesHandler.GetServiceByID)
27+
rg.GET("/services/:sid", servicesHandler.GetServiceByID)
2828
rg.GET("/services/available-instances", servicesHandler.GetAvailableInstances)
2929
rg.POST("/services/assemble", servicesHandler.AssembleService)
3030

3131
// 服务操作路由
32-
rg.POST("/services/:sid/:type/start", servicesHandler.StartService)
33-
rg.POST("/services/:sid/:type/stop", servicesHandler.StopService)
34-
rg.POST("/services/:sid/:type/restart", servicesHandler.RestartService)
35-
rg.DELETE("/services/:sid/:type", servicesHandler.DeleteService)
36-
rg.PUT("/services/:sid/:type/rename", servicesHandler.RenameService)
37-
rg.POST("/services/:sid/:type/dissolve", servicesHandler.DissolveService)
38-
rg.POST("/services/:sid/:type/sync", servicesHandler.SyncService)
32+
rg.POST("/services/:sid/start", servicesHandler.StartService)
33+
rg.POST("/services/:sid/stop", servicesHandler.StopService)
34+
rg.POST("/services/:sid/restart", servicesHandler.RestartService)
35+
rg.DELETE("/services/:sid", servicesHandler.DeleteService)
36+
rg.PUT("/services/:sid/rename", servicesHandler.RenameService)
37+
rg.POST("/services/:sid/dissolve", servicesHandler.DissolveService)
38+
rg.POST("/services/:sid/sync", servicesHandler.SyncService)
3939
}
4040

4141
// GetServices 获取所有服务
@@ -57,9 +57,8 @@ func (h *ServicesHandler) GetServices(c *gin.Context) {
5757
// GetServiceByID 根据 SID 和 Type 获取单个服务
5858
func (h *ServicesHandler) GetServiceByID(c *gin.Context) {
5959
sid := c.Param("sid")
60-
serviceType := c.Param("type")
6160

62-
service, err := h.servicesService.GetServiceByID(sid, serviceType)
61+
service, err := h.servicesService.GetServiceByID(sid)
6362
if err != nil {
6463
c.JSON(http.StatusNotFound, gin.H{"error": "服务不存在"})
6564
return
@@ -111,9 +110,8 @@ func (h *ServicesHandler) AssembleService(c *gin.Context) {
111110
// StartService 启动服务
112111
func (h *ServicesHandler) StartService(c *gin.Context) {
113112
sid := c.Param("sid")
114-
serviceType := c.Param("type")
115113

116-
if err := h.servicesService.StartService(sid, serviceType); err != nil {
114+
if err := h.servicesService.StartService(sid); err != nil {
117115
c.JSON(http.StatusInternalServerError, gin.H{"error": "启动服务失败: " + err.Error()})
118116
return
119117
}
@@ -127,9 +125,8 @@ func (h *ServicesHandler) StartService(c *gin.Context) {
127125
// StopService 停止服务
128126
func (h *ServicesHandler) StopService(c *gin.Context) {
129127
sid := c.Param("sid")
130-
serviceType := c.Param("type")
131128

132-
if err := h.servicesService.StopService(sid, serviceType); err != nil {
129+
if err := h.servicesService.StopService(sid); err != nil {
133130
c.JSON(http.StatusInternalServerError, gin.H{"error": "停止服务失败: " + err.Error()})
134131
return
135132
}
@@ -143,9 +140,8 @@ func (h *ServicesHandler) StopService(c *gin.Context) {
143140
// RestartService 重启服务
144141
func (h *ServicesHandler) RestartService(c *gin.Context) {
145142
sid := c.Param("sid")
146-
serviceType := c.Param("type")
147143

148-
if err := h.servicesService.RestartService(sid, serviceType); err != nil {
144+
if err := h.servicesService.RestartService(sid); err != nil {
149145
c.JSON(http.StatusInternalServerError, gin.H{"error": "重启服务失败: " + err.Error()})
150146
return
151147
}
@@ -159,9 +155,8 @@ func (h *ServicesHandler) RestartService(c *gin.Context) {
159155
// DeleteService 删除服务
160156
func (h *ServicesHandler) DeleteService(c *gin.Context) {
161157
sid := c.Param("sid")
162-
serviceType := c.Param("type")
163158

164-
if err := h.servicesService.DeleteService(sid, serviceType); err != nil {
159+
if err := h.servicesService.DeleteService(sid); err != nil {
165160
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除服务失败: " + err.Error()})
166161
return
167162
}
@@ -175,18 +170,15 @@ func (h *ServicesHandler) DeleteService(c *gin.Context) {
175170
// RenameService 重命名服务
176171
func (h *ServicesHandler) RenameService(c *gin.Context) {
177172
sid := c.Param("sid")
178-
serviceType := c.Param("type")
179-
180173
var req struct {
181174
Name string `json:"name" binding:"required"`
182175
}
183-
184176
if err := c.ShouldBindJSON(&req); err != nil {
185177
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误: " + err.Error()})
186178
return
187179
}
188180

189-
if err := h.servicesService.RenameService(sid, serviceType, req.Name); err != nil {
181+
if err := h.servicesService.RenameService(sid, req.Name); err != nil {
190182
c.JSON(http.StatusInternalServerError, gin.H{"error": "重命名服务失败: " + err.Error()})
191183
return
192184
}
@@ -200,9 +192,8 @@ func (h *ServicesHandler) RenameService(c *gin.Context) {
200192
// DissolveService 解散服务
201193
func (h *ServicesHandler) DissolveService(c *gin.Context) {
202194
sid := c.Param("sid")
203-
serviceType := c.Param("type")
204195

205-
if err := h.servicesService.DissolveService(sid, serviceType); err != nil {
196+
if err := h.servicesService.DissolveService(sid); err != nil {
206197
c.JSON(http.StatusInternalServerError, gin.H{"error": "解散服务失败: " + err.Error()})
207198
return
208199
}
@@ -216,9 +207,8 @@ func (h *ServicesHandler) DissolveService(c *gin.Context) {
216207
// SyncService 同步服务
217208
func (h *ServicesHandler) SyncService(c *gin.Context) {
218209
sid := c.Param("sid")
219-
serviceType := c.Param("type")
220210

221-
if err := h.servicesService.SyncService(sid, serviceType); err != nil {
211+
if err := h.servicesService.SyncService(sid); err != nil {
222212
c.JSON(http.StatusInternalServerError, gin.H{"error": "同步服务失败: " + err.Error()})
223213
return
224214
}

internal/api/tunnel.go

Lines changed: 132 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"time"
2424

2525
"github.com/gin-gonic/gin"
26+
"github.com/google/uuid"
2627
"gorm.io/gorm"
2728
)
2829

@@ -40,39 +41,6 @@ func NewTunnelHandler(tunnelService *tunnel.Service, sseManager *sse.Manager) *T
4041
}
4142
}
4243

43-
// normalizeCommandLine 规范化 commandLine,将 URL 参数按字母顺序排序
44-
// 这样可以确保参数顺序不同但内容相同的 URL 能够正确比较
45-
func normalizeCommandLine(commandLine string) (string, error) {
46-
// 查找 ? 的位置
47-
idx := strings.Index(commandLine, "?")
48-
if idx == -1 {
49-
// 没有查询参数,直接返回
50-
return commandLine, nil
51-
}
52-
53-
// 分离基础部分和查询参数部分
54-
base := commandLine[:idx]
55-
queryStr := commandLine[idx+1:]
56-
57-
// 解析查询参数
58-
values, err := url.ParseQuery(queryStr)
59-
if err != nil {
60-
return "", fmt.Errorf("解析查询参数失败: %w", err)
61-
}
62-
63-
// 使用 Encode() 方法重建查询参数
64-
// Encode() 会自动:
65-
// 1. 按字母顺序排序键
66-
// 2. 正确处理 URL 编码
67-
// 3. 正确处理裸键和多值参数
68-
encoded := values.Encode()
69-
if encoded == "" {
70-
return base, nil
71-
}
72-
73-
return base + "?" + encoded, nil
74-
}
75-
7644
// setupTunnelRoutes 设置隧道相关路由
7745
func SetupTunnelRoutes(rg *gin.RouterGroup, tunnelService *tunnel.Service, sseManager *sse.Manager, sseProcessor *metrics.SSEProcessor) {
7846
// 创建TunnelHandler实例
@@ -707,22 +675,17 @@ func (h *TunnelHandler) HandleSetTunnelRestart(c *gin.Context) {
707675

708676
// HandleGetTunnelDetails 获取隧道详细信息 (GET /api/tunnels/{id}/details)
709677
func (h *TunnelHandler) HandleGetTunnelDetails(c *gin.Context) {
710-
idStr := c.Param("id")
711-
if idStr == "" {
712-
c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "缺少隧道ID"})
713-
return
714-
}
715-
id, err := strconv.ParseInt(idStr, 10, 64)
716-
if err != nil {
717-
c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "无效的隧道ID"})
678+
instanceId := c.Param("id")
679+
if instanceId == "" {
680+
c.JSON(http.StatusBadRequest, map[string]interface{}{"error": "缺少实例ID"})
718681
return
719682
}
720683

721684
db := h.tunnelService.GormDB()
722685

723-
// 使用 GORM 查询隧道及关联的端点信息,自动处理 serializer:json
686+
// 使用 GORM 根据 instanceId 查询隧道及关联的端点信息
724687
var tunnel models.Tunnel
725-
if err := db.Preload("Endpoint").First(&tunnel, id).Error; err != nil {
688+
if err := db.Preload("Endpoint").Where("instance_id = ?", instanceId).First(&tunnel).Error; err != nil {
726689
if err == gorm.ErrRecordNotFound {
727690
c.JSON(http.StatusNotFound, map[string]interface{}{"error": "隧道不存在"})
728691
return
@@ -793,16 +756,10 @@ func (h *TunnelHandler) HandleGetTunnelDetails(c *gin.Context) {
793756
}
794757
}
795758

796-
// 处理 InstanceID 字段
797-
instanceID := ""
798-
if tunnel.InstanceID != nil {
799-
instanceID = *tunnel.InstanceID
800-
}
801-
802759
// 组装扁平化响应结构
803760
resp := map[string]interface{}{
804761
"id": tunnel.ID,
805-
"instanceId": instanceID,
762+
"instanceId": tunnel.InstanceID,
806763
"name": tunnel.Name,
807764
"type": tunnel.Type,
808765
"status": statusType, // 简化为字符串
@@ -1345,8 +1302,40 @@ func (h *TunnelHandler) HandleTemplateCreate(c *gin.Context) {
13451302
return
13461303
}
13471304

1348-
// 获取创建的隧道ID
1305+
// 获取创建的隧道ID和instanceID
13491306
tunnelID, _ := h.getTunnelIDByName(tunnelName)
1307+
1308+
// 生成服务ID并更新peer信息
1309+
if tunnelID > 0 {
1310+
// 获取隧道的instanceID
1311+
instanceID, err := h.tunnelService.GetInstanceIDByTunnelID(tunnelID)
1312+
if err == nil {
1313+
// 生成UUID作为sid
1314+
sid := uuid.New().String()
1315+
1316+
// 构建peer对象
1317+
peer := &models.Peer{
1318+
SID: &sid,
1319+
Type: func() *string {
1320+
t := "0" // single对应type=0
1321+
return &t
1322+
}(),
1323+
}
1324+
1325+
// 如果TunnelName有值,设置alias
1326+
if req.TunnelName != "" {
1327+
peer.Alias = &req.TunnelName
1328+
}
1329+
1330+
// 调用UpdateInstancePeers更新peer信息
1331+
_, err := nodepass.UpdateInstancePeers(req.Inbounds.MasterID, instanceID, peer)
1332+
if err != nil {
1333+
log.Warnf("[API] 更新隧道peer信息失败,但不影响创建: %v", err)
1334+
} else {
1335+
log.Infof("[API] 成功更新隧道peer信息: sid=%s, type=0, alias=%s", sid, req.TunnelName)
1336+
}
1337+
}
1338+
}
13501339
// if err == nil {
13511340
// groupName := endpointName
13521341
// if err := h.createTunnelGroup(groupName, "single", "单端转发分组", []int64{tunnelID}); err != nil {
@@ -1514,13 +1503,58 @@ func (h *TunnelHandler) HandleTemplateCreate(c *gin.Context) {
15141503
log.Infof("[API] 步骤2完成: client端隧道创建成功")
15151504
log.Infof("[API] 双端隧道创建完成")
15161505

1517-
// 获取创建的隧道ID并自动创建分组
1506+
// 生成服务ID(双端模式共用一个sid)
1507+
sid := uuid.New().String()
1508+
1509+
// 获取创建的隧道ID并更新peer信息
15181510
var tunnelIDs []int64
15191511
if serverTunnelID, err := h.getTunnelIDByName(serverTunnelName); err == nil {
15201512
tunnelIDs = append(tunnelIDs, serverTunnelID)
1513+
1514+
// 更新server端隧道的peer信息
1515+
if instanceID, err := h.tunnelService.GetInstanceIDByTunnelID(serverTunnelID); err == nil {
1516+
peer := &models.Peer{
1517+
SID: &sid,
1518+
Type: func() *string {
1519+
t := "2" // bothway对应type=2
1520+
return &t
1521+
}(),
1522+
}
1523+
if req.TunnelName != "" {
1524+
peer.Alias = &req.TunnelName
1525+
}
1526+
1527+
_, err := nodepass.UpdateInstancePeers(serverConfig.MasterID, instanceID, peer)
1528+
if err != nil {
1529+
log.Warnf("[API] 更新server端隧道peer信息失败: %v", err)
1530+
} else {
1531+
log.Infof("[API] 成功更新server端隧道peer信息: sid=%s, type=2", sid)
1532+
}
1533+
}
15211534
}
15221535
if clientTunnelID, err := h.getTunnelIDByName(clientTunnelName); err == nil {
15231536
tunnelIDs = append(tunnelIDs, clientTunnelID)
1537+
1538+
// 更新client端隧道的peer信息
1539+
if instanceID, err := h.tunnelService.GetInstanceIDByTunnelID(clientTunnelID); err == nil {
1540+
peer := &models.Peer{
1541+
SID: &sid,
1542+
Type: func() *string {
1543+
t := "2" // bothway对应type=2
1544+
return &t
1545+
}(),
1546+
}
1547+
if req.TunnelName != "" {
1548+
peer.Alias = &req.TunnelName
1549+
}
1550+
1551+
_, err := nodepass.UpdateInstancePeers(clientConfig.MasterID, instanceID, peer)
1552+
if err != nil {
1553+
log.Warnf("[API] 更新client端隧道peer信息失败: %v", err)
1554+
} else {
1555+
log.Infof("[API] 成功更新client端隧道peer信息: sid=%s, type=2", sid)
1556+
}
1557+
}
15241558
}
15251559

15261560
// if len(tunnelIDs) > 0 {
@@ -1690,13 +1724,58 @@ func (h *TunnelHandler) HandleTemplateCreate(c *gin.Context) {
16901724
log.Infof("[API] 步骤2完成: client端隧道创建成功")
16911725
log.Infof("[API] 内网穿透隧道创建完成")
16921726

1693-
// 获取创建的隧道ID并自动创建分组
1727+
// 生成服务ID(内网穿透模式共用一个sid)
1728+
sid := uuid.New().String()
1729+
1730+
// 获取创建的隧道ID并更新peer信息
16941731
var tunnelIDs []int64
16951732
if serverTunnelID, err := h.getTunnelIDByName(serverTunnelName); err == nil {
16961733
tunnelIDs = append(tunnelIDs, serverTunnelID)
1734+
1735+
// 更新server端隧道的peer信息
1736+
if instanceID, err := h.tunnelService.GetInstanceIDByTunnelID(serverTunnelID); err == nil {
1737+
peer := &models.Peer{
1738+
SID: &sid,
1739+
Type: func() *string {
1740+
t := "1" // intranet对应type=1
1741+
return &t
1742+
}(),
1743+
}
1744+
if req.TunnelName != "" {
1745+
peer.Alias = &req.TunnelName
1746+
}
1747+
1748+
_, err := nodepass.UpdateInstancePeers(serverConfig.MasterID, instanceID, peer)
1749+
if err != nil {
1750+
log.Warnf("[API] 更新server端隧道peer信息失败: %v", err)
1751+
} else {
1752+
log.Infof("[API] 成功更新server端隧道peer信息: sid=%s, type=1", sid)
1753+
}
1754+
}
16971755
}
16981756
if clientTunnelID, err := h.getTunnelIDByName(clientTunnelName); err == nil {
16991757
tunnelIDs = append(tunnelIDs, clientTunnelID)
1758+
1759+
// 更新client端隧道的peer信息
1760+
if instanceID, err := h.tunnelService.GetInstanceIDByTunnelID(clientTunnelID); err == nil {
1761+
peer := &models.Peer{
1762+
SID: &sid,
1763+
Type: func() *string {
1764+
t := "1" // intranet对应type=1
1765+
return &t
1766+
}(),
1767+
}
1768+
if req.TunnelName != "" {
1769+
peer.Alias = &req.TunnelName
1770+
}
1771+
1772+
_, err := nodepass.UpdateInstancePeers(clientConfig.MasterID, instanceID, peer)
1773+
if err != nil {
1774+
log.Warnf("[API] 更新client端隧道peer信息失败: %v", err)
1775+
} else {
1776+
log.Infof("[API] 成功更新client端隧道peer信息: sid=%s, type=1", sid)
1777+
}
1778+
}
17001779
}
17011780

17021781
// if len(tunnelIDs) > 0 {

0 commit comments

Comments
 (0)