Skip to content

Commit da5db4e

Browse files
committed
feat:适配core1.4.4支持的重置实例
1 parent 8bdb03f commit da5db4e

7 files changed

Lines changed: 229 additions & 33 deletions

File tree

app/tunnels/components/quick-create-tunnel-modal.tsx

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ export default function QuickCreateTunnelModal({ isOpen, onOpenChange, onSaved,
7474

7575
// 新增:重置流量checkbox,仅编辑模式下显示
7676
const [resetChecked, setResetChecked] = useState(false);
77-
const [resetLoading, setResetLoading] = useState(false);
7877

7978
// 表单数据
8079
const [formData, setFormData] = useState({
@@ -187,7 +186,8 @@ export default function QuickCreateTunnelModal({ isOpen, onOpenChange, onSaved,
187186
logLevel,
188187
password: password || undefined,
189188
min: mode==='client' && min !== '' ? min : undefined,
190-
max: mode==='client' && max !== '' ? max : undefined
189+
max: mode==='client' && max !== '' ? max : undefined,
190+
resetTraffic: modalMode === 'edit' ? resetChecked : undefined
191191
})
192192
});
193193
const data = await res.json();
@@ -196,26 +196,6 @@ export default function QuickCreateTunnelModal({ isOpen, onOpenChange, onSaved,
196196
onOpenChange(false);
197197
onSaved?.();
198198

199-
// 新增:重置流量逻辑
200-
if (mode === 'server' && tlsMode === 'mode2' && certPath.trim() && keyPath.trim() && editData?.id) {
201-
setResetLoading(true);
202-
try {
203-
const resp = await fetch(`${buildApiUrl(`/api/tunnels/${editData.id}`)}`, {
204-
method: 'PATCH',
205-
headers: { 'Content-Type': 'application/json' },
206-
body: JSON.stringify({ action: 'reset' }),
207-
});
208-
const data = await resp.json();
209-
if (resp.ok && data.success) {
210-
addToast({ title: '流量统计已重置', description: '流量统计已成功重置', color: 'success' });
211-
}
212-
} catch (e) {
213-
addToast({ title: '流量统计重置失败', description: '流量统计重置失败', color: 'danger' });
214-
} finally {
215-
setResetLoading(false);
216-
}
217-
}
218-
219199
} catch (err) {
220200
addToast({ title: modalMode==='edit'?'更新失败':"创建失败", description: err instanceof Error ? err.message : "未知错误", color: "danger" });
221201
} finally {
@@ -416,7 +396,6 @@ export default function QuickCreateTunnelModal({ isOpen, onOpenChange, onSaved,
416396
id="reset-traffic"
417397
isSelected={resetChecked}
418398
onValueChange={setResetChecked}
419-
isDisabled={resetLoading}
420399
size="sm"
421400
>
422401
保存后重置流量统计

app/tunnels/details/page.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,10 @@ export default function TunnelDetailPage({ params }: { params: Promise<PageParam
639639
const response = await fetch(`/api/tunnels/${tunnelInfo.id}`, {
640640
method: 'PATCH',
641641
headers: { 'Content-Type': 'application/json' },
642-
body: JSON.stringify({ action: 'reset' }),
642+
body: JSON.stringify({
643+
action: 'reset',
644+
instanceId: tunnelInfo.instanceId
645+
}),
643646
});
644647
const data = await response.json();
645648
if (response.ok && data.success) {
@@ -769,6 +772,7 @@ export default function TunnelDetailPage({ params }: { params: Promise<PageParam
769772
>
770773
<span className="hidden sm:inline">重置</span>
771774
</Button>
775+
772776
</div>
773777
</div>
774778

@@ -848,6 +852,7 @@ export default function TunnelDetailPage({ params }: { params: Promise<PageParam
848852
<Button color="default" variant="light" onPress={onClose} size="sm">
849853
取消
850854
</Button>
855+
851856
<Button
852857
color="secondary"
853858
size="sm"

components/layout/navbar-menu.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ const navigationItems = [
2323
label: "实例管理",
2424
icon: "solar:transmission-bold",
2525
},
26-
{
27-
href: "/groups",
28-
label: "分组场景",
29-
icon: "solar:layers-bold",
30-
},
26+
// {
27+
// href: "/groups",
28+
// label: "分组场景",
29+
// icon: "solar:layers-bold",
30+
// },
3131
{
3232
href: "/endpoints",
3333
label: "主控管理",

internal/api/endpoint.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,10 @@ func (h *EndpointHandler) refreshTunnels(endpointID int64) error {
10881088
v, _ := strconv.Atoi(p)
10891089
return v
10901090
}
1091-
convInt := func(s string) int {
1091+
convInt := func(s string) interface{} {
1092+
if s == "" {
1093+
return nil
1094+
}
10921095
v, _ := strconv.Atoi(s)
10931096
return v
10941097
}

internal/api/tunnel.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func (h *TunnelHandler) HandleCreateTunnel(w http.ResponseWriter, r *http.Reques
152152
return
153153
}
154154

155-
// 对于未设置的min/max,使用-1表示"未设置"状态
155+
// 对于未设置的min/max,使用-1表示"未设置"状态(在请求中传递,但数据库存储时会转为NULL)
156156
if !minSet {
157157
minVal = -1
158158
}
@@ -749,6 +749,31 @@ func (h *TunnelHandler) HandlePatchTunnels(w http.ResponseWriter, r *http.Reques
749749
Message: "操作成功",
750750
})
751751

752+
case "reset":
753+
if raw.InstanceID == "" {
754+
w.WriteHeader(http.StatusBadRequest)
755+
json.NewEncoder(w).Encode(tunnel.TunnelResponse{
756+
Success: false,
757+
Error: "重置操作需提供有效的隧道实例ID(instanceId)",
758+
})
759+
return
760+
}
761+
762+
// 重置隧道的流量统计信息
763+
if err := h.tunnelService.ResetTunnelTrafficByInstanceID(raw.InstanceID); err != nil {
764+
w.WriteHeader(http.StatusBadRequest)
765+
json.NewEncoder(w).Encode(tunnel.TunnelResponse{
766+
Success: false,
767+
Error: err.Error(),
768+
})
769+
return
770+
}
771+
772+
json.NewEncoder(w).Encode(tunnel.TunnelResponse{
773+
Success: true,
774+
Message: "隧道流量信息重置成功",
775+
})
776+
752777
case "rename":
753778
if raw.ID == 0 || raw.Name == "" {
754779
w.WriteHeader(http.StatusBadRequest)
@@ -777,7 +802,7 @@ func (h *TunnelHandler) HandlePatchTunnels(w http.ResponseWriter, r *http.Reques
777802
w.WriteHeader(http.StatusBadRequest)
778803
json.NewEncoder(w).Encode(tunnel.TunnelResponse{
779804
Success: false,
780-
Error: "无效的操作类型,支持: start, stop, restart, rename",
805+
Error: "无效的操作类型,支持: start, stop, restart, reset, rename",
781806
})
782807
}
783808
}
@@ -2713,6 +2738,7 @@ func (h *TunnelHandler) HandleUpdateTunnelV2(w http.ResponseWriter, r *http.Requ
27132738
Password string `json:"password"`
27142739
Min json.RawMessage `json:"min"`
27152740
Max json.RawMessage `json:"max"`
2741+
ResetTraffic bool `json:"resetTraffic"` // 新增:是否重置流量统计
27162742
}
27172743
if err := json.NewDecoder(r.Body).Decode(&raw); err != nil {
27182744
w.WriteHeader(http.StatusBadRequest)
@@ -2890,7 +2916,31 @@ func (h *TunnelHandler) HandleUpdateTunnelV2(w http.ResponseWriter, r *http.Requ
28902916
maxVal = -1
28912917
}
28922918
_, _ = h.tunnelService.DB().Exec(`UPDATE "Tunnel" SET name = ?, mode = ?, tunnelAddress = ?, tunnelPort = ?, targetAddress = ?, targetPort = ?, tlsMode = ?, certPath = ?, keyPath = ?, logLevel = ?, commandLine = ?, min = ?, max = ?, status = ?, updatedAt = ? WHERE id = ?`,
2893-
raw.Name, raw.Mode, raw.TunnelAddress, tunnelPort, raw.TargetAddress, targetPort, raw.TLSMode, raw.CertPath, raw.KeyPath, raw.LogLevel, commandLine, minVal, maxVal, "running", time.Now(), tunnelID)
2919+
raw.Name, raw.Mode, raw.TunnelAddress, tunnelPort, raw.TargetAddress, targetPort, raw.TLSMode, raw.CertPath, raw.KeyPath, raw.LogLevel, commandLine,
2920+
func() interface{} {
2921+
if minVal >= 0 {
2922+
return minVal
2923+
}
2924+
return nil
2925+
}(),
2926+
func() interface{} {
2927+
if maxVal >= 0 {
2928+
return maxVal
2929+
}
2930+
return nil
2931+
}(),
2932+
"running", time.Now(), tunnelID)
2933+
}
2934+
2935+
// 如果需要重置流量统计
2936+
if raw.ResetTraffic {
2937+
log.Infof("[API] 编辑实例后重置流量统计: tunnelID=%d", tunnelID)
2938+
if err := h.tunnelService.ResetTunnelTrafficByInstanceID(instanceID); err != nil {
2939+
log.Errorf("[API] 重置流量统计失败: %v", err)
2940+
// 不返回错误,因为主要操作已经成功,只是重置失败
2941+
} else {
2942+
log.Infof("[API] 流量统计重置成功: tunnelID=%d", tunnelID)
2943+
}
28942944
}
28952945

28962946
json.NewEncoder(w).Encode(tunnel.TunnelResponse{Success: true, Message: "编辑实例成功"})

internal/nodepass/client.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,16 @@ func (c *Client) SetRestartInstance(instanceID string, restart bool) error {
127127
return nil
128128
}
129129

130+
// ResetInstanceTraffic 重置指定实例的流量统计 (PATCH /instances/{id})
131+
func (c *Client) ResetInstanceTraffic(instanceID string) error {
132+
payload := map[string]string{"action": "reset"}
133+
url := fmt.Sprintf("%s%s/instances/%s", c.baseURL, c.apiPath, instanceID)
134+
if err := c.doRequest(http.MethodPatch, url, payload, nil); err != nil {
135+
return err
136+
}
137+
return nil
138+
}
139+
130140
// GetInfo 获取NodePass实例的系统信息
131141
func (c *Client) GetInfo() (*NodePassInfo, error) {
132142
url := fmt.Sprintf("%s%s/info", c.baseURL, c.apiPath)

internal/tunnel/service.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2209,3 +2209,152 @@ func (s *Service) ClearOperationLogs() (int64, error) {
22092209
}
22102210
return rows, nil
22112211
}
2212+
2213+
// ResetTunnelTraffic 重置隧道的流量统计信息
2214+
func (s *Service) ResetTunnelTraffic(tunnelID int64) error {
2215+
log.Infof("[API] 重置隧道流量统计: tunnelID=%d", tunnelID)
2216+
2217+
// 获取隧道和端点信息
2218+
var tunnel struct {
2219+
InstanceID string
2220+
EndpointID int64
2221+
Name string
2222+
}
2223+
var endpoint struct {
2224+
URL string
2225+
APIPath string
2226+
APIKey string
2227+
}
2228+
2229+
// 先调用 NodePass API 重置流量统计
2230+
npClient := nodepass.NewClient(endpoint.URL, endpoint.APIPath, endpoint.APIKey, nil)
2231+
if err := npClient.ResetInstanceTraffic(tunnel.InstanceID); err != nil {
2232+
// 检查是否为 404 错误(旧版本 NodePass 不支持)
2233+
if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "Not Found") {
2234+
log.Warnf("[API] NodePass API 不支持重置流量功能(可能是旧版本): %v", err)
2235+
return errors.New("当前实例不支持重置流量功能")
2236+
} else {
2237+
log.Errorf("[API] NodePass API 重置流量失败: %v", err)
2238+
return fmt.Errorf("NodePass API 重置流量失败: %v", err)
2239+
}
2240+
}
2241+
2242+
// 只有 NodePass API 调用成功后才更新数据库
2243+
_, err := s.db.Exec(`
2244+
UPDATE "Tunnel"
2245+
SET
2246+
tcpRx = 0,
2247+
tcpTx = 0,
2248+
udpRx = 0,
2249+
udpTx = 0,
2250+
pool = NULL,
2251+
ping = NULL,
2252+
updatedAt = ?
2253+
WHERE id = ?
2254+
`, time.Now(), tunnelID)
2255+
if err != nil {
2256+
log.Errorf("[API] 数据库重置流量统计失败: %v", err)
2257+
return fmt.Errorf("数据库重置流量统计失败: %v", err)
2258+
}
2259+
2260+
// 记录操作日志
2261+
_, err = s.db.Exec(`
2262+
INSERT INTO "TunnelOperationLog" (
2263+
tunnelId, tunnelName, action, status, message
2264+
) VALUES (?, ?, ?, ?, ?)
2265+
`,
2266+
tunnelID,
2267+
tunnel.Name,
2268+
"reset_traffic",
2269+
"success",
2270+
"重置流量统计信息",
2271+
)
2272+
if err != nil {
2273+
log.Errorf("[API] 记录重置流量日志失败: %v", err)
2274+
// 不返回错误,因为主要操作已经成功
2275+
}
2276+
2277+
log.Infof("[API] 隧道流量统计重置成功: tunnelID=%d, name=%s", tunnelID, tunnel.Name)
2278+
return nil
2279+
}
2280+
2281+
// ResetTunnelTrafficByInstanceID 根据实例ID重置隧道的流量统计信息
2282+
func (s *Service) ResetTunnelTrafficByInstanceID(instanceID string) error {
2283+
log.Infof("[API] 根据实例ID重置隧道流量统计: instanceID=%s", instanceID)
2284+
2285+
// 获取隧道和端点信息
2286+
var tunnel struct {
2287+
ID int64
2288+
Name string
2289+
EndpointID int64
2290+
}
2291+
var endpoint struct {
2292+
URL string
2293+
APIPath string
2294+
APIKey string
2295+
}
2296+
2297+
err := s.db.QueryRow(`
2298+
SELECT t.id, t.name, t.endpointId, e.url, e.apiPath, e.apiKey
2299+
FROM "Tunnel" t
2300+
JOIN "Endpoint" e ON t.endpointId = e.id
2301+
WHERE t.instanceId = ?
2302+
`, instanceID).Scan(&tunnel.ID, &tunnel.Name, &tunnel.EndpointID, &endpoint.URL, &endpoint.APIPath, &endpoint.APIKey)
2303+
if err != nil {
2304+
if err == sql.ErrNoRows {
2305+
return errors.New("隧道不存在")
2306+
}
2307+
return fmt.Errorf("查询隧道失败: %v", err)
2308+
}
2309+
2310+
// 先调用 NodePass API 重置流量统计
2311+
npClient := nodepass.NewClient(endpoint.URL, endpoint.APIPath, endpoint.APIKey, nil)
2312+
if err := npClient.ResetInstanceTraffic(instanceID); err != nil {
2313+
// 检查是否为 404 错误(旧版本 NodePass 不支持)
2314+
if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "Not Found") {
2315+
log.Warnf("[API] NodePass API 不支持重置流量功能(可能是旧版本): %v", err)
2316+
return errors.New("当前实例不支持重置流量功能")
2317+
} else {
2318+
log.Errorf("[API] NodePass API 重置流量失败: %v", err)
2319+
return fmt.Errorf("NodePass API 重置流量失败: %v", err)
2320+
}
2321+
}
2322+
2323+
// 只有 NodePass API 调用成功后才更新数据库
2324+
_, err = s.db.Exec(`
2325+
UPDATE "Tunnel"
2326+
SET
2327+
tcpRx = 0,
2328+
tcpTx = 0,
2329+
udpRx = 0,
2330+
udpTx = 0,
2331+
pool = NULL,
2332+
ping = NULL,
2333+
updatedAt = ?
2334+
WHERE instanceId = ?
2335+
`, time.Now(), instanceID)
2336+
if err != nil {
2337+
log.Errorf("[API] 数据库重置流量统计失败: %v", err)
2338+
return fmt.Errorf("数据库重置流量统计失败: %v", err)
2339+
}
2340+
2341+
// 记录操作日志
2342+
_, err = s.db.Exec(`
2343+
INSERT INTO "TunnelOperationLog" (
2344+
tunnelId, tunnelName, action, status, message
2345+
) VALUES (?, ?, ?, ?, ?)
2346+
`,
2347+
tunnel.ID,
2348+
tunnel.Name,
2349+
"reset_traffic",
2350+
"success",
2351+
"重置流量统计信息",
2352+
)
2353+
if err != nil {
2354+
log.Errorf("[API] 记录重置流量日志失败: %v", err)
2355+
// 不返回错误,因为主要操作已经成功
2356+
}
2357+
2358+
log.Infof("[API] 隧道流量统计重置成功: instanceID=%s, name=%s", instanceID, tunnel.Name)
2359+
return nil
2360+
}

0 commit comments

Comments
 (0)