From a76ba823eef8f72c6ebd46a8a2011eff2eed6538 Mon Sep 17 00:00:00 2001 From: Mxmilu666 Date: Tue, 14 Oct 2025 21:33:38 +0800 Subject: [PATCH] feat(api): add endpoint to kick proxy by name --- server/control.go | 22 ++++++++++++++++++++++ server/dashboard_api.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/server/control.go b/server/control.go index af9c9de3..607027c6 100644 --- a/server/control.go +++ b/server/control.go @@ -94,6 +94,28 @@ func (cm *ControlManager) Close() error { return nil } +// KickByProxyName finds the Control that manages the given proxy (tunnel) name and closes +// the entire control connection (disconnects the frpc). Returns an error if no such proxy is found. +func (cm *ControlManager) KickByProxyName(proxyName string) error { + cm.mu.RLock() + var target *Control + for _, ctl := range cm.ctlsByRunID { + ctl.mu.RLock() + _, ok := ctl.proxies[proxyName] + ctl.mu.RUnlock() + if ok { + target = ctl + break + } + } + cm.mu.RUnlock() + + if target == nil { + return fmt.Errorf("no proxy found with name [%s]", proxyName) + } + return target.Close() +} + type Control struct { // all resource managers and controllers rc *controller.ResourceController diff --git a/server/dashboard_api.go b/server/dashboard_api.go index 54e5d9e9..09f4ea38 100644 --- a/server/dashboard_api.go +++ b/server/dashboard_api.go @@ -52,6 +52,7 @@ func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) subRouter.HandleFunc("/api/serverinfo", svr.apiServerInfo).Methods("GET") subRouter.HandleFunc("/api/proxy/{type}", svr.apiProxyByType).Methods("GET") subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.apiProxyByTypeAndName).Methods("GET") + subRouter.HandleFunc("/api/proxy/{type}/{name}/kick", svr.apiKickProxyByTypeAndName).Methods("POST") subRouter.HandleFunc("/api/traffic/{name}", svr.apiProxyTraffic).Methods("GET") subRouter.HandleFunc("/api/proxies", svr.deleteProxies).Methods("DELETE") @@ -308,6 +309,37 @@ func (svr *Service) apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request res.Msg = string(buf) } +// POST /api/proxy/:type/:name/kick +// Kick the client (frpc) that owns the proxy with given name. +func (svr *Service) apiKickProxyByTypeAndName(w http.ResponseWriter, r *http.Request) { + res := GeneralResponse{Code: 200} + params := mux.Vars(r) + name := params["name"] + + defer func() { + log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code) + w.WriteHeader(res.Code) + if len(res.Msg) > 0 { + _, _ = w.Write([]byte(res.Msg)) + } + }() + log.Infof("http request: [%s]", r.URL.Path) + + if name == "" { + res.Code = 400 + res.Msg = "proxy name required" + return + } + + if err := svr.ctlManager.KickByProxyName(name); err != nil { + res.Code = 404 + res.Msg = err.Error() + return + } + + res.Msg = "ok" +} + func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) { proxyInfo.Name = proxyName ps := mem.StatsCollector.GetProxiesByTypeAndName(proxyType, proxyName)