mirror of
https://github.com/fatedier/frp.git
synced 2026-07-16 09:19:17 +08:00
feat: add system prune API v2 (#5395)
This commit is contained in:
@@ -95,9 +95,7 @@ func (m *serverMetrics) clearUselessInfo(continuousOfflineDuration time.Duration
|
|||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
total = len(m.info.ProxyStatistics)
|
total = len(m.info.ProxyStatistics)
|
||||||
for name, data := range m.info.ProxyStatistics {
|
for name, data := range m.info.ProxyStatistics {
|
||||||
if !data.LastCloseTime.IsZero() &&
|
if m.shouldClearProxyStats(data, continuousOfflineDuration) {
|
||||||
data.LastStartTime.Before(data.LastCloseTime) &&
|
|
||||||
m.clock.Since(data.LastCloseTime) > continuousOfflineDuration {
|
|
||||||
delete(m.info.ProxyStatistics, name)
|
delete(m.info.ProxyStatistics, name)
|
||||||
count++
|
count++
|
||||||
log.Tracef("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
log.Tracef("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
||||||
@@ -106,10 +104,20 @@ func (m *serverMetrics) clearUselessInfo(continuousOfflineDuration time.Duration
|
|||||||
return count, total
|
return count, total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) shouldClearProxyStats(data *ProxyStatistics, continuousOfflineDuration time.Duration) bool {
|
||||||
|
return !data.LastCloseTime.IsZero() &&
|
||||||
|
data.LastStartTime.Before(data.LastCloseTime) &&
|
||||||
|
m.clock.Since(data.LastCloseTime) > continuousOfflineDuration
|
||||||
|
}
|
||||||
|
|
||||||
func (m *serverMetrics) ClearOfflineProxies() (int, int) {
|
func (m *serverMetrics) ClearOfflineProxies() (int, int) {
|
||||||
return m.clearUselessInfo(0)
|
return m.clearUselessInfo(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *serverMetrics) PruneOfflineProxies() (int, int) {
|
||||||
|
return m.clearUselessInfo(0)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *serverMetrics) NewClient() {
|
func (m *serverMetrics) NewClient() {
|
||||||
m.info.ClientCounts.Inc(1)
|
m.info.ClientCounts.Inc(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,70 @@ func TestServerMetricsClearUselessInfoUsesClock(t *testing.T) {
|
|||||||
require.Empty(metrics.info.ProxyStatistics)
|
require.Empty(metrics.info.ProxyStatistics)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServerMetricsClearOfflineProxiesPreservesLegacyTotal(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
start := time.Date(2026, time.May, 8, 12, 30, 0, 0, time.UTC)
|
||||||
|
clk := clocktesting.NewFakeClock(start.Add(time.Minute))
|
||||||
|
metrics := newServerMetricsWithClock(clk)
|
||||||
|
metrics.info.ProxyStatistics["offline"] = &ProxyStatistics{
|
||||||
|
Name: "offline",
|
||||||
|
LastStartTime: start.Add(-time.Hour),
|
||||||
|
LastCloseTime: start,
|
||||||
|
}
|
||||||
|
metrics.info.ProxyStatistics["online"] = &ProxyStatistics{
|
||||||
|
Name: "online",
|
||||||
|
LastStartTime: start,
|
||||||
|
}
|
||||||
|
|
||||||
|
cleared, total := metrics.ClearOfflineProxies()
|
||||||
|
|
||||||
|
require.Equal(1, cleared)
|
||||||
|
require.Equal(2, total)
|
||||||
|
require.False(metrics.hasProxyStatistics("offline"))
|
||||||
|
require.True(metrics.hasProxyStatistics("online"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerMetricsPruneOfflineProxiesReportsTotalStats(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
start := time.Date(2026, time.May, 8, 12, 30, 0, 0, time.UTC)
|
||||||
|
clk := clocktesting.NewFakeClock(start.Add(time.Minute))
|
||||||
|
metrics := newServerMetricsWithClock(clk)
|
||||||
|
metrics.info.ProxyStatistics["offline"] = &ProxyStatistics{
|
||||||
|
Name: "offline",
|
||||||
|
LastStartTime: start.Add(-time.Hour),
|
||||||
|
LastCloseTime: start,
|
||||||
|
}
|
||||||
|
metrics.info.ProxyStatistics["online"] = &ProxyStatistics{
|
||||||
|
Name: "online",
|
||||||
|
LastStartTime: start,
|
||||||
|
}
|
||||||
|
metrics.info.ProxyStatistics["restarted"] = &ProxyStatistics{
|
||||||
|
Name: "restarted",
|
||||||
|
LastStartTime: start.Add(30 * time.Second),
|
||||||
|
LastCloseTime: start,
|
||||||
|
}
|
||||||
|
metrics.info.ProxyStatistics["same-time"] = &ProxyStatistics{
|
||||||
|
Name: "same-time",
|
||||||
|
LastStartTime: start,
|
||||||
|
LastCloseTime: start,
|
||||||
|
}
|
||||||
|
|
||||||
|
cleared, total := metrics.PruneOfflineProxies()
|
||||||
|
|
||||||
|
require.Equal(1, cleared)
|
||||||
|
require.Equal(4, total)
|
||||||
|
require.False(metrics.hasProxyStatistics("offline"))
|
||||||
|
require.True(metrics.hasProxyStatistics("online"))
|
||||||
|
require.True(metrics.hasProxyStatistics("restarted"))
|
||||||
|
require.True(metrics.hasProxyStatistics("same-time"))
|
||||||
|
|
||||||
|
cleared, total = metrics.PruneOfflineProxies()
|
||||||
|
require.Equal(0, cleared)
|
||||||
|
require.Equal(3, total)
|
||||||
|
}
|
||||||
|
|
||||||
func TestServerMetricsRunUsesClockTicker(t *testing.T) {
|
func TestServerMetricsRunUsesClockTicker(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
|
|||||||
@@ -85,4 +85,5 @@ type Collector interface {
|
|||||||
GetProxyByName(proxyName string) *ProxyStats
|
GetProxyByName(proxyName string) *ProxyStats
|
||||||
GetProxyTraffic(name string) *ProxyTrafficInfo
|
GetProxyTraffic(name string) *ProxyTrafficInfo
|
||||||
ClearOfflineProxies() (int, int)
|
ClearOfflineProxies() (int, int)
|
||||||
|
PruneOfflineProxies() (int, int)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper)
|
|||||||
|
|
||||||
subRouter.HandleFunc("/api/v2/users", httppkg.MakeHTTPHandlerFuncV2(apiController.APIV2UserList)).Methods("GET")
|
subRouter.HandleFunc("/api/v2/users", httppkg.MakeHTTPHandlerFuncV2(apiController.APIV2UserList)).Methods("GET")
|
||||||
subRouter.HandleFunc("/api/v2/system/info", httppkg.MakeHTTPHandlerFuncV2(apiController.APIV2SystemInfo)).Methods("GET")
|
subRouter.HandleFunc("/api/v2/system/info", httppkg.MakeHTTPHandlerFuncV2(apiController.APIV2SystemInfo)).Methods("GET")
|
||||||
|
subRouter.HandleFunc("/api/v2/system/prune", httppkg.MakeHTTPHandlerFuncV2(apiController.APIV2SystemPrune)).Methods("POST")
|
||||||
subRouter.HandleFunc("/api/v2/clients", httppkg.MakeHTTPHandlerFuncV2(apiController.APIV2ClientList)).Methods("GET")
|
subRouter.HandleFunc("/api/v2/clients", httppkg.MakeHTTPHandlerFuncV2(apiController.APIV2ClientList)).Methods("GET")
|
||||||
v2EncodedPathRouter := subRouter.NewRoute().Subrouter()
|
v2EncodedPathRouter := subRouter.NewRoute().Subrouter()
|
||||||
v2EncodedPathRouter.UseEncodedPath()
|
v2EncodedPathRouter.UseEncodedPath()
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ const (
|
|||||||
defaultV2Page = 1
|
defaultV2Page = 1
|
||||||
defaultV2PageSize = 50
|
defaultV2PageSize = 50
|
||||||
maxV2PageSize = 200
|
maxV2PageSize = 200
|
||||||
|
|
||||||
|
v2SystemPruneTypeOfflineProxies = "offline_proxies"
|
||||||
)
|
)
|
||||||
|
|
||||||
var apiV2ProxyTypes = []string{
|
var apiV2ProxyTypes = []string{
|
||||||
@@ -82,6 +84,21 @@ func (c *Controller) APIV2SystemInfo(ctx *httppkg.Context) (any, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /api/v2/system/prune
|
||||||
|
func (c *Controller) APIV2SystemPrune(ctx *httppkg.Context) (any, error) {
|
||||||
|
pruneType, err := parseV2SystemPruneType(ctx.Query("type"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cleared, total := mem.StatsCollector.PruneOfflineProxies()
|
||||||
|
return model.V2SystemPruneResp{
|
||||||
|
Type: pruneType,
|
||||||
|
Cleared: cleared,
|
||||||
|
Total: total,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// /api/v2/users
|
// /api/v2/users
|
||||||
func (c *Controller) APIV2UserList(ctx *httppkg.Context) (any, error) {
|
func (c *Controller) APIV2UserList(ctx *httppkg.Context) (any, error) {
|
||||||
page, pageSize, err := parseV2PageParams(ctx)
|
page, pageSize, err := parseV2PageParams(ctx)
|
||||||
@@ -321,6 +338,18 @@ func parseV2ProxyTypeFilter(raw string) (string, error) {
|
|||||||
return "", httppkg.NewError(http.StatusBadRequest, "type must be one of tcp, udp, http, https, tcpmux, stcp, xtcp, sudp")
|
return "", httppkg.NewError(http.StatusBadRequest, "type must be one of tcp, udp, http, https, tcpmux, stcp, xtcp, sudp")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseV2SystemPruneType(raw string) (string, error) {
|
||||||
|
pruneType := strings.ToLower(raw)
|
||||||
|
switch pruneType {
|
||||||
|
case "":
|
||||||
|
return "", httppkg.NewError(http.StatusBadRequest, "type is required")
|
||||||
|
case v2SystemPruneTypeOfflineProxies:
|
||||||
|
return pruneType, nil
|
||||||
|
default:
|
||||||
|
return "", httppkg.NewError(http.StatusBadRequest, "type must be one of offline_proxies")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func matchV2StatusFilter(online bool, filter string) bool {
|
func matchV2StatusFilter(online bool, filter string) bool {
|
||||||
switch filter {
|
switch filter {
|
||||||
case "", "all":
|
case "", "all":
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ type v2EnvelopeForTest[T any] struct {
|
|||||||
type fakeStatsCollector struct {
|
type fakeStatsCollector struct {
|
||||||
server *mem.ServerStats
|
server *mem.ServerStats
|
||||||
proxies map[string]*mem.ProxyStats
|
proxies map[string]*mem.ProxyStats
|
||||||
|
pruneable map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeStatsCollector) GetServer() *mem.ServerStats {
|
func (f *fakeStatsCollector) GetServer() *mem.ServerStats {
|
||||||
@@ -82,6 +83,19 @@ func (f *fakeStatsCollector) ClearOfflineProxies() (int, int) {
|
|||||||
return 0, len(f.proxies)
|
return 0, len(f.proxies)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeStatsCollector) PruneOfflineProxies() (int, int) {
|
||||||
|
total := len(f.proxies)
|
||||||
|
cleared := 0
|
||||||
|
for name := range f.pruneable {
|
||||||
|
if _, ok := f.proxies[name]; ok {
|
||||||
|
delete(f.proxies, name)
|
||||||
|
cleared++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.pruneable = map[string]bool{}
|
||||||
|
return cleared, total
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIV2SystemInfoEnvelope(t *testing.T) {
|
func TestAPIV2SystemInfoEnvelope(t *testing.T) {
|
||||||
oldStatsCollector := mem.StatsCollector
|
oldStatsCollector := mem.StatsCollector
|
||||||
mem.StatsCollector = &fakeStatsCollector{
|
mem.StatsCollector = &fakeStatsCollector{
|
||||||
@@ -184,6 +198,88 @@ func TestAPIV2SystemInfoEnvelope(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIV2SystemPruneOfflineProxies(t *testing.T) {
|
||||||
|
oldStatsCollector := mem.StatsCollector
|
||||||
|
collector := &fakeStatsCollector{
|
||||||
|
proxies: map[string]*mem.ProxyStats{
|
||||||
|
"tcp-offline": {Name: "tcp-offline", Type: "tcp"},
|
||||||
|
"http-offline": {Name: "http-offline", Type: "http"},
|
||||||
|
"udp-offline": {Name: "udp-offline", Type: "udp"},
|
||||||
|
"tcp-online": {Name: "tcp-online", Type: "tcp"},
|
||||||
|
"http-online": {Name: "http-online", Type: "http"},
|
||||||
|
"udp-online": {Name: "udp-online", Type: "udp"},
|
||||||
|
"stcp-restarted": {Name: "stcp-restarted", Type: "stcp"},
|
||||||
|
"xtcp-restarted": {Name: "xtcp-restarted", Type: "xtcp"},
|
||||||
|
"sudp-same-time": {Name: "sudp-same-time", Type: "sudp"},
|
||||||
|
"tcpmux-running": {Name: "tcpmux-running", Type: "tcpmux"},
|
||||||
|
},
|
||||||
|
pruneable: map[string]bool{
|
||||||
|
"tcp-offline": true,
|
||||||
|
"http-offline": true,
|
||||||
|
"udp-offline": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mem.StatsCollector = collector
|
||||||
|
t.Cleanup(func() {
|
||||||
|
mem.StatsCollector = oldStatsCollector
|
||||||
|
})
|
||||||
|
|
||||||
|
controller := NewController(&v1.ServerConfig{}, registry.NewClientRegistry(), serverproxy.NewManager())
|
||||||
|
router := newV2TestRouter(controller)
|
||||||
|
|
||||||
|
resp := performRequestWithMethod(router, http.MethodPost, "/api/v2/system/prune?type=offline_proxies")
|
||||||
|
if resp.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status mismatch, want %d got %d, body: %s", http.StatusOK, resp.Code, resp.Body.String())
|
||||||
|
}
|
||||||
|
rawResp := decodeResponse[v2EnvelopeForTest[map[string]json.RawMessage]](t, resp)
|
||||||
|
if rawResp.Code != http.StatusOK || rawResp.Msg != "success" {
|
||||||
|
t.Fatalf("envelope mismatch: %#v", rawResp)
|
||||||
|
}
|
||||||
|
assertRawJSONKeys(t, rawResp.Data, "cleared", "total", "type")
|
||||||
|
pruneResp := decodeResponse[v2EnvelopeForTest[model.V2SystemPruneResp]](t, resp)
|
||||||
|
if pruneResp.Data.Type != "offline_proxies" || pruneResp.Data.Cleared != 3 || pruneResp.Data.Total != 10 {
|
||||||
|
t.Fatalf("prune response mismatch: %#v", pruneResp.Data)
|
||||||
|
}
|
||||||
|
if _, ok := collector.proxies["tcp-offline"]; ok {
|
||||||
|
t.Fatal("pruned proxy statistics should be removed")
|
||||||
|
}
|
||||||
|
if _, ok := collector.proxies["tcp-online"]; !ok {
|
||||||
|
t.Fatal("online proxy statistics should remain")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = performRequestWithMethod(router, http.MethodPost, "/api/v2/system/prune?type=offline_proxies")
|
||||||
|
if resp.Code != http.StatusOK {
|
||||||
|
t.Fatalf("second prune status mismatch, want %d got %d", http.StatusOK, resp.Code)
|
||||||
|
}
|
||||||
|
pruneResp = decodeResponse[v2EnvelopeForTest[model.V2SystemPruneResp]](t, resp)
|
||||||
|
if pruneResp.Data.Cleared != 0 || pruneResp.Data.Total != 7 {
|
||||||
|
t.Fatalf("second prune response mismatch: %#v", pruneResp.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIV2SystemPruneTypeErrorsUseEnvelope(t *testing.T) {
|
||||||
|
controller := newV2TestController(t)
|
||||||
|
router := newV2TestRouter(controller)
|
||||||
|
|
||||||
|
resp := performRequestWithMethod(router, http.MethodPost, "/api/v2/system/prune")
|
||||||
|
if resp.Code != http.StatusBadRequest {
|
||||||
|
t.Fatalf("missing type status mismatch, want %d got %d", http.StatusBadRequest, resp.Code)
|
||||||
|
}
|
||||||
|
errResp := decodeResponse[httppkg.V2Response](t, resp)
|
||||||
|
if errResp.Code != http.StatusBadRequest || errResp.Msg != "type is required" || errResp.Data != nil {
|
||||||
|
t.Fatalf("missing type error envelope mismatch: %#v", errResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = performRequestWithMethod(router, http.MethodPost, "/api/v2/system/prune?type=clients")
|
||||||
|
if resp.Code != http.StatusBadRequest {
|
||||||
|
t.Fatalf("invalid type status mismatch, want %d got %d", http.StatusBadRequest, resp.Code)
|
||||||
|
}
|
||||||
|
errResp = decodeResponse[httppkg.V2Response](t, resp)
|
||||||
|
if errResp.Code != http.StatusBadRequest || errResp.Msg != "type must be one of offline_proxies" || errResp.Data != nil {
|
||||||
|
t.Fatalf("invalid type error envelope mismatch: %#v", errResp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIV2ClientListEnvelopePaginationAndFilters(t *testing.T) {
|
func TestAPIV2ClientListEnvelopePaginationAndFilters(t *testing.T) {
|
||||||
controller := newV2TestController(t)
|
controller := newV2TestController(t)
|
||||||
router := newV2TestRouter(controller)
|
router := newV2TestRouter(controller)
|
||||||
@@ -527,6 +623,7 @@ func newV2TestRouter(controller *Controller) *mux.Router {
|
|||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
router.HandleFunc("/api/v2/users", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2UserList)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v2/users", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2UserList)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v2/system/info", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2SystemInfo)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v2/system/info", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2SystemInfo)).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc("/api/v2/system/prune", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2SystemPrune)).Methods(http.MethodPost)
|
||||||
router.HandleFunc("/api/v2/clients", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2ClientList)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v2/clients", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2ClientList)).Methods(http.MethodGet)
|
||||||
encodedPathRouter := router.NewRoute().Subrouter()
|
encodedPathRouter := router.NewRoute().Subrouter()
|
||||||
encodedPathRouter.UseEncodedPath()
|
encodedPathRouter.UseEncodedPath()
|
||||||
@@ -540,7 +637,11 @@ func newV2TestRouter(controller *Controller) *mux.Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func performRequest(handler http.Handler, target string) *httptest.ResponseRecorder {
|
func performRequest(handler http.Handler, target string) *httptest.ResponseRecorder {
|
||||||
req := httptest.NewRequest(http.MethodGet, target, nil)
|
return performRequestWithMethod(handler, http.MethodGet, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func performRequestWithMethod(handler http.Handler, method, target string) *httptest.ResponseRecorder {
|
||||||
|
req := httptest.NewRequest(method, target, nil)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
handler.ServeHTTP(resp, req)
|
handler.ServeHTTP(resp, req)
|
||||||
return resp
|
return resp
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ type V2SystemInfoStatusResp struct {
|
|||||||
ProxyTypeCounts map[string]int64 `json:"proxyTypeCount"`
|
ProxyTypeCounts map[string]int64 `json:"proxyTypeCount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type V2SystemPruneResp struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Cleared int `json:"cleared"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
type V2UserResp struct {
|
type V2UserResp struct {
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
ClientCount int `json:"clientCount"`
|
ClientCount int `json:"clientCount"`
|
||||||
|
|||||||
@@ -98,6 +98,13 @@ export const http = {
|
|||||||
request<T>(url, { ...options, method: 'GET' }),
|
request<T>(url, { ...options, method: 'GET' }),
|
||||||
getV2: <T>(url: string, options?: RequestInit) =>
|
getV2: <T>(url: string, options?: RequestInit) =>
|
||||||
requestV2<T>(url, { ...options, method: 'GET' }),
|
requestV2<T>(url, { ...options, method: 'GET' }),
|
||||||
|
postV2: <T>(url: string, body?: any, options?: RequestInit) =>
|
||||||
|
requestV2<T>(url, {
|
||||||
|
...options,
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', ...options?.headers },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
}),
|
||||||
post: <T>(url: string, body?: any, options?: RequestInit) =>
|
post: <T>(url: string, body?: any, options?: RequestInit) =>
|
||||||
request<T>(url, {
|
request<T>(url, {
|
||||||
...options,
|
...options,
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import type {
|
|||||||
TrafficResponse,
|
TrafficResponse,
|
||||||
} from '../types/proxy'
|
} from '../types/proxy'
|
||||||
|
|
||||||
|
export interface SystemPruneResponse {
|
||||||
|
type: 'offline_proxies'
|
||||||
|
cleared: number
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
|
||||||
export const getProxiesByType = (type: string) => {
|
export const getProxiesByType = (type: string) => {
|
||||||
return http.get<GetProxyResponse>(`../api/proxy/${type}`)
|
return http.get<GetProxyResponse>(`../api/proxy/${type}`)
|
||||||
}
|
}
|
||||||
@@ -66,5 +72,7 @@ export const getProxyTraffic = (name: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const clearOfflineProxies = () => {
|
export const clearOfflineProxies = () => {
|
||||||
return http.delete('../api/proxies?status=offline')
|
return http.postV2<SystemPruneResponse>(
|
||||||
|
'../api/v2/system/prune?type=offline_proxies',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user