mirror of
https://github.com/fatedier/frp.git
synced 2026-07-16 09:19:17 +08:00
feat: add system info API v2 (#5394)
This commit is contained in:
@@ -49,6 +49,7 @@ func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper)
|
|||||||
subRouter.HandleFunc("/api/proxies", httppkg.MakeHTTPHandlerFunc(apiController.DeleteProxies)).Methods("DELETE")
|
subRouter.HandleFunc("/api/proxies", httppkg.MakeHTTPHandlerFunc(apiController.DeleteProxies)).Methods("DELETE")
|
||||||
|
|
||||||
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/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()
|
||||||
|
|||||||
@@ -58,8 +58,12 @@ func NewController(
|
|||||||
|
|
||||||
// /api/serverinfo
|
// /api/serverinfo
|
||||||
func (c *Controller) APIServerInfo(ctx *httppkg.Context) (any, error) {
|
func (c *Controller) APIServerInfo(ctx *httppkg.Context) (any, error) {
|
||||||
|
return c.buildServerInfoResp(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) buildServerInfoResp() model.ServerInfoResp {
|
||||||
serverStats := mem.StatsCollector.GetServer()
|
serverStats := mem.StatsCollector.GetServer()
|
||||||
svrResp := model.ServerInfoResp{
|
return model.ServerInfoResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
BindPort: c.serverCfg.BindPort,
|
BindPort: c.serverCfg.BindPort,
|
||||||
VhostHTTPPort: c.serverCfg.VhostHTTPPort,
|
VhostHTTPPort: c.serverCfg.VhostHTTPPort,
|
||||||
@@ -80,8 +84,6 @@ func (c *Controller) APIServerInfo(ctx *httppkg.Context) (any, error) {
|
|||||||
ClientCounts: serverStats.ClientCounts,
|
ClientCounts: serverStats.ClientCounts,
|
||||||
ProxyTypeCounts: serverStats.ProxyTypeCounts,
|
ProxyTypeCounts: serverStats.ProxyTypeCounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
return svrResp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// /api/clients
|
// /api/clients
|
||||||
|
|||||||
@@ -48,6 +48,40 @@ var apiV2ProxyTypes = []string{
|
|||||||
string(v1.ProxyTypeSUDP),
|
string(v1.ProxyTypeSUDP),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /api/v2/system/info
|
||||||
|
func (c *Controller) APIV2SystemInfo(ctx *httppkg.Context) (any, error) {
|
||||||
|
info := c.buildServerInfoResp()
|
||||||
|
proxyTypeCounts := info.ProxyTypeCounts
|
||||||
|
if proxyTypeCounts == nil {
|
||||||
|
proxyTypeCounts = map[string]int64{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.V2SystemInfoResp{
|
||||||
|
Version: info.Version,
|
||||||
|
Config: model.V2SystemInfoConfigResp{
|
||||||
|
BindPort: info.BindPort,
|
||||||
|
VhostHTTPPort: info.VhostHTTPPort,
|
||||||
|
VhostHTTPSPort: info.VhostHTTPSPort,
|
||||||
|
TCPMuxHTTPConnectPort: info.TCPMuxHTTPConnectPort,
|
||||||
|
KCPBindPort: info.KCPBindPort,
|
||||||
|
QUICBindPort: info.QUICBindPort,
|
||||||
|
SubdomainHost: info.SubdomainHost,
|
||||||
|
MaxPoolCount: info.MaxPoolCount,
|
||||||
|
MaxPortsPerClient: info.MaxPortsPerClient,
|
||||||
|
HeartbeatTimeout: info.HeartBeatTimeout,
|
||||||
|
AllowPortsStr: info.AllowPortsStr,
|
||||||
|
TLSForce: info.TLSForce,
|
||||||
|
},
|
||||||
|
Status: model.V2SystemInfoStatusResp{
|
||||||
|
TotalTrafficIn: info.TotalTrafficIn,
|
||||||
|
TotalTrafficOut: info.TotalTrafficOut,
|
||||||
|
CurConns: info.CurConns,
|
||||||
|
ClientCounts: info.ClientCounts,
|
||||||
|
ProxyTypeCounts: proxyTypeCounts,
|
||||||
|
},
|
||||||
|
}, 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)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config/types"
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/metrics/mem"
|
"github.com/fatedier/frp/pkg/metrics/mem"
|
||||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||||
@@ -40,10 +41,14 @@ type v2EnvelopeForTest[T any] struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type fakeStatsCollector struct {
|
type fakeStatsCollector struct {
|
||||||
|
server *mem.ServerStats
|
||||||
proxies map[string]*mem.ProxyStats
|
proxies map[string]*mem.ProxyStats
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeStatsCollector) GetServer() *mem.ServerStats {
|
func (f *fakeStatsCollector) GetServer() *mem.ServerStats {
|
||||||
|
if f.server != nil {
|
||||||
|
return f.server
|
||||||
|
}
|
||||||
return &mem.ServerStats{ProxyTypeCounts: map[string]int64{}}
|
return &mem.ServerStats{ProxyTypeCounts: map[string]int64{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +82,108 @@ func (f *fakeStatsCollector) ClearOfflineProxies() (int, int) {
|
|||||||
return 0, len(f.proxies)
|
return 0, len(f.proxies)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIV2SystemInfoEnvelope(t *testing.T) {
|
||||||
|
oldStatsCollector := mem.StatsCollector
|
||||||
|
mem.StatsCollector = &fakeStatsCollector{
|
||||||
|
server: &mem.ServerStats{
|
||||||
|
TotalTrafficIn: 1024,
|
||||||
|
TotalTrafficOut: 2048,
|
||||||
|
CurConns: 3,
|
||||||
|
ClientCounts: 4,
|
||||||
|
ProxyTypeCounts: map[string]int64{
|
||||||
|
"tcp": 2,
|
||||||
|
"http": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
proxies: map[string]*mem.ProxyStats{},
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
mem.StatsCollector = oldStatsCollector
|
||||||
|
})
|
||||||
|
|
||||||
|
controller := NewController(&v1.ServerConfig{
|
||||||
|
BindPort: 7000,
|
||||||
|
VhostHTTPPort: 8080,
|
||||||
|
VhostHTTPSPort: 8443,
|
||||||
|
TCPMuxHTTPConnectPort: 9000,
|
||||||
|
KCPBindPort: 7001,
|
||||||
|
QUICBindPort: 7002,
|
||||||
|
SubDomainHost: "example.com",
|
||||||
|
MaxPortsPerClient: 8,
|
||||||
|
AllowPorts: []types.PortsRange{
|
||||||
|
{Start: 1000, End: 1002},
|
||||||
|
{Single: 2000},
|
||||||
|
},
|
||||||
|
Transport: v1.ServerTransportConfig{
|
||||||
|
MaxPoolCount: 5,
|
||||||
|
HeartbeatTimeout: 90,
|
||||||
|
TLS: v1.TLSServerConfig{
|
||||||
|
Force: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, registry.NewClientRegistry(), serverproxy.NewManager())
|
||||||
|
router := newV2TestRouter(controller)
|
||||||
|
|
||||||
|
resp := performRequest(router, "/api/v2/system/info")
|
||||||
|
if resp.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status mismatch, want %d got %d", http.StatusOK, resp.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, "config", "status", "version")
|
||||||
|
assertRawJSONKeysFromMessage(t, rawResp.Data["config"],
|
||||||
|
"allowPortsStr",
|
||||||
|
"bindPort",
|
||||||
|
"heartbeatTimeout",
|
||||||
|
"kcpBindPort",
|
||||||
|
"maxPoolCount",
|
||||||
|
"maxPortsPerClient",
|
||||||
|
"quicBindPort",
|
||||||
|
"subdomainHost",
|
||||||
|
"tcpmuxHTTPConnectPort",
|
||||||
|
"tlsForce",
|
||||||
|
"vhostHTTPPort",
|
||||||
|
"vhostHTTPSPort",
|
||||||
|
)
|
||||||
|
assertRawJSONKeysFromMessage(t, rawResp.Data["status"],
|
||||||
|
"clientCounts",
|
||||||
|
"curConns",
|
||||||
|
"proxyTypeCount",
|
||||||
|
"totalTrafficIn",
|
||||||
|
"totalTrafficOut",
|
||||||
|
)
|
||||||
|
|
||||||
|
systemResp := decodeResponse[v2EnvelopeForTest[model.V2SystemInfoResp]](t, resp)
|
||||||
|
if systemResp.Data.Version == "" {
|
||||||
|
t.Fatal("version should be set at top level")
|
||||||
|
}
|
||||||
|
if systemResp.Data.Config.BindPort != 7000 ||
|
||||||
|
systemResp.Data.Config.VhostHTTPPort != 8080 ||
|
||||||
|
systemResp.Data.Config.VhostHTTPSPort != 8443 ||
|
||||||
|
systemResp.Data.Config.TCPMuxHTTPConnectPort != 9000 ||
|
||||||
|
systemResp.Data.Config.KCPBindPort != 7001 ||
|
||||||
|
systemResp.Data.Config.QUICBindPort != 7002 ||
|
||||||
|
systemResp.Data.Config.SubdomainHost != "example.com" ||
|
||||||
|
systemResp.Data.Config.MaxPoolCount != 5 ||
|
||||||
|
systemResp.Data.Config.MaxPortsPerClient != 8 ||
|
||||||
|
systemResp.Data.Config.HeartbeatTimeout != 90 ||
|
||||||
|
systemResp.Data.Config.AllowPortsStr != "1000-1002,2000" ||
|
||||||
|
!systemResp.Data.Config.TLSForce {
|
||||||
|
t.Fatalf("config mismatch: %#v", systemResp.Data.Config)
|
||||||
|
}
|
||||||
|
if systemResp.Data.Status.TotalTrafficIn != 1024 ||
|
||||||
|
systemResp.Data.Status.TotalTrafficOut != 2048 ||
|
||||||
|
systemResp.Data.Status.CurConns != 3 ||
|
||||||
|
systemResp.Data.Status.ClientCounts != 4 ||
|
||||||
|
systemResp.Data.Status.ProxyTypeCounts["tcp"] != 2 ||
|
||||||
|
systemResp.Data.Status.ProxyTypeCounts["http"] != 1 {
|
||||||
|
t.Fatalf("status mismatch: %#v", systemResp.Data.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIV2ClientListEnvelopePaginationAndFilters(t *testing.T) {
|
func TestAPIV2ClientListEnvelopePaginationAndFilters(t *testing.T) {
|
||||||
controller := newV2TestController(t)
|
controller := newV2TestController(t)
|
||||||
router := newV2TestRouter(controller)
|
router := newV2TestRouter(controller)
|
||||||
@@ -322,7 +429,26 @@ func TestLegacyAPIResponsesRemainBare(t *testing.T) {
|
|||||||
controller := newV2TestController(t)
|
controller := newV2TestController(t)
|
||||||
router := newV2TestRouter(controller)
|
router := newV2TestRouter(controller)
|
||||||
|
|
||||||
resp := performRequest(router, "/api/clients")
|
resp := performRequest(router, "/api/serverinfo")
|
||||||
|
var serverInfo model.ServerInfoResp
|
||||||
|
if err := json.Unmarshal(resp.Body.Bytes(), &serverInfo); err != nil {
|
||||||
|
t.Fatalf("legacy serverinfo should be a bare object: %v, body: %s", err, resp.Body.String())
|
||||||
|
}
|
||||||
|
if serverInfo.Version == "" {
|
||||||
|
t.Fatal("legacy serverinfo version should be set")
|
||||||
|
}
|
||||||
|
var serverInfoRaw map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(resp.Body.Bytes(), &serverInfoRaw); err != nil {
|
||||||
|
t.Fatalf("unmarshal legacy serverinfo object failed: %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := serverInfoRaw["data"]; ok {
|
||||||
|
t.Fatalf("legacy serverinfo should not use v2 envelope: %s", resp.Body.String())
|
||||||
|
}
|
||||||
|
if _, ok := serverInfoRaw["config"]; ok {
|
||||||
|
t.Fatalf("legacy serverinfo should stay flat, got config in: %s", resp.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = performRequest(router, "/api/clients")
|
||||||
var clients []model.ClientInfoResp
|
var clients []model.ClientInfoResp
|
||||||
if err := json.Unmarshal(resp.Body.Bytes(), &clients); err != nil {
|
if err := json.Unmarshal(resp.Body.Bytes(), &clients); err != nil {
|
||||||
t.Fatalf("legacy clients should be a bare array: %v, body: %s", err, resp.Body.String())
|
t.Fatalf("legacy clients should be a bare array: %v, body: %s", err, resp.Body.String())
|
||||||
@@ -400,12 +526,14 @@ func newV2TestController(t *testing.T) *Controller {
|
|||||||
func newV2TestRouter(controller *Controller) *mux.Router {
|
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/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()
|
||||||
encodedPathRouter.HandleFunc("/api/v2/clients/{key}", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2ClientDetail)).Methods(http.MethodGet)
|
encodedPathRouter.HandleFunc("/api/v2/clients/{key}", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2ClientDetail)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v2/proxies", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2ProxyList)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v2/proxies", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2ProxyList)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v2/proxies/{name}", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2ProxyDetail)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v2/proxies/{name}", httppkg.MakeHTTPHandlerFuncV2(controller.APIV2ProxyDetail)).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc("/api/serverinfo", httppkg.MakeHTTPHandlerFunc(controller.APIServerInfo)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/clients", httppkg.MakeHTTPHandlerFunc(controller.APIClientList)).Methods(http.MethodGet)
|
router.HandleFunc("/api/clients", httppkg.MakeHTTPHandlerFunc(controller.APIClientList)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/proxy/{type}", httppkg.MakeHTTPHandlerFunc(controller.APIProxyByType)).Methods(http.MethodGet)
|
router.HandleFunc("/api/proxy/{type}", httppkg.MakeHTTPHandlerFunc(controller.APIProxyByType)).Methods(http.MethodGet)
|
||||||
return router
|
return router
|
||||||
@@ -427,3 +555,26 @@ func decodeResponse[T any](t *testing.T, resp *httptest.ResponseRecorder) T {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertRawJSONKeys(t *testing.T, raw map[string]json.RawMessage, want ...string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if len(raw) != len(want) {
|
||||||
|
t.Fatalf("json keys mismatch, want %v got %v", want, raw)
|
||||||
|
}
|
||||||
|
for _, key := range want {
|
||||||
|
if _, ok := raw[key]; !ok {
|
||||||
|
t.Fatalf("json key %q missing from %v", key, raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertRawJSONKeysFromMessage(t *testing.T, raw json.RawMessage, want ...string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var out map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(raw, &out); err != nil {
|
||||||
|
t.Fatalf("unmarshal raw json object failed: %v, body: %s", err, string(raw))
|
||||||
|
}
|
||||||
|
assertRawJSONKeys(t, out, want...)
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,35 @@ type V2PageResp[T any] struct {
|
|||||||
Items []T `json:"items"`
|
Items []T `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type V2SystemInfoResp struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Config V2SystemInfoConfigResp `json:"config"`
|
||||||
|
Status V2SystemInfoStatusResp `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type V2SystemInfoConfigResp struct {
|
||||||
|
BindPort int `json:"bindPort"`
|
||||||
|
VhostHTTPPort int `json:"vhostHTTPPort"`
|
||||||
|
VhostHTTPSPort int `json:"vhostHTTPSPort"`
|
||||||
|
TCPMuxHTTPConnectPort int `json:"tcpmuxHTTPConnectPort"`
|
||||||
|
KCPBindPort int `json:"kcpBindPort"`
|
||||||
|
QUICBindPort int `json:"quicBindPort"`
|
||||||
|
SubdomainHost string `json:"subdomainHost"`
|
||||||
|
MaxPoolCount int64 `json:"maxPoolCount"`
|
||||||
|
MaxPortsPerClient int64 `json:"maxPortsPerClient"`
|
||||||
|
HeartbeatTimeout int64 `json:"heartbeatTimeout"`
|
||||||
|
AllowPortsStr string `json:"allowPortsStr"`
|
||||||
|
TLSForce bool `json:"tlsForce"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type V2SystemInfoStatusResp struct {
|
||||||
|
TotalTrafficIn int64 `json:"totalTrafficIn"`
|
||||||
|
TotalTrafficOut int64 `json:"totalTrafficOut"`
|
||||||
|
CurConns int64 `json:"curConns"`
|
||||||
|
ClientCounts int64 `json:"clientCounts"`
|
||||||
|
ProxyTypeCounts map[string]int64 `json:"proxyTypeCount"`
|
||||||
|
}
|
||||||
|
|
||||||
type V2UserResp struct {
|
type V2UserResp struct {
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
ClientCount int `json:"clientCount"`
|
ClientCount int `json:"clientCount"`
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ import { http } from './http'
|
|||||||
import type { ServerInfo } from '../types/server'
|
import type { ServerInfo } from '../types/server'
|
||||||
|
|
||||||
export const getServerInfo = () => {
|
export const getServerInfo = () => {
|
||||||
return http.get<ServerInfo>('../api/serverinfo')
|
return http.getV2<ServerInfo>('../api/v2/system/info')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
export interface ServerInfo {
|
export interface ServerInfo {
|
||||||
version: string
|
version: string
|
||||||
|
config: ServerInfoConfig
|
||||||
|
status: ServerInfoStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerInfoConfig {
|
||||||
bindPort: number
|
bindPort: number
|
||||||
vhostHTTPPort: number
|
vhostHTTPPort: number
|
||||||
vhostHTTPSPort: number
|
vhostHTTPSPort: number
|
||||||
@@ -12,8 +17,9 @@ export interface ServerInfo {
|
|||||||
heartbeatTimeout: number
|
heartbeatTimeout: number
|
||||||
allowPortsStr: string
|
allowPortsStr: string
|
||||||
tlsForce: boolean
|
tlsForce: boolean
|
||||||
|
}
|
||||||
|
|
||||||
// Stats
|
export interface ServerInfoStatus {
|
||||||
totalTrafficIn: number
|
totalTrafficIn: number
|
||||||
totalTrafficOut: number
|
totalTrafficOut: number
|
||||||
curConns: number
|
curConns: number
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ import {
|
|||||||
import { getServerInfo } from '../api/server'
|
import { getServerInfo } from '../api/server'
|
||||||
import ProxyCard from '../components/ProxyCard.vue'
|
import ProxyCard from '../components/ProxyCard.vue'
|
||||||
import type { ProxyStatsInfo } from '../types/proxy'
|
import type { ProxyStatsInfo } from '../types/proxy'
|
||||||
|
import type { ServerInfo } from '../types/server'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -183,16 +184,9 @@ const total = ref(0)
|
|||||||
let requestSeq = 0
|
let requestSeq = 0
|
||||||
let searchDebounceTimer: number | null = null
|
let searchDebounceTimer: number | null = null
|
||||||
|
|
||||||
type ServerInfoLite = {
|
let serverInfoPromise: Promise<ServerInfo> | null = null
|
||||||
vhostHTTPPort: number
|
|
||||||
vhostHTTPSPort: number
|
|
||||||
tcpmuxHTTPConnectPort: number
|
|
||||||
subdomainHost: string
|
|
||||||
}
|
|
||||||
|
|
||||||
let serverInfoPromise: Promise<ServerInfoLite> | null = null
|
const fetchServerInfo = (): Promise<ServerInfo> => {
|
||||||
|
|
||||||
const fetchServerInfo = (): Promise<ServerInfoLite> => {
|
|
||||||
if (!serverInfoPromise) {
|
if (!serverInfoPromise) {
|
||||||
serverInfoPromise = getServerInfo().catch((err) => {
|
serverInfoPromise = getServerInfo().catch((err) => {
|
||||||
serverInfoPromise = null
|
serverInfoPromise = null
|
||||||
@@ -232,25 +226,33 @@ const convertProxy = async (
|
|||||||
}
|
}
|
||||||
if (type === 'http') {
|
if (type === 'http') {
|
||||||
const info = await fetchServerInfo()
|
const info = await fetchServerInfo()
|
||||||
if (info && info.vhostHTTPPort) {
|
if (info && info.config.vhostHTTPPort) {
|
||||||
return new HTTPProxy(proxy, info.vhostHTTPPort, info.subdomainHost)
|
return new HTTPProxy(
|
||||||
|
proxy,
|
||||||
|
info.config.vhostHTTPPort,
|
||||||
|
info.config.subdomainHost,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (type === 'https') {
|
if (type === 'https') {
|
||||||
const info = await fetchServerInfo()
|
const info = await fetchServerInfo()
|
||||||
if (info && info.vhostHTTPSPort) {
|
if (info && info.config.vhostHTTPSPort) {
|
||||||
return new HTTPSProxy(proxy, info.vhostHTTPSPort, info.subdomainHost)
|
return new HTTPSProxy(
|
||||||
|
proxy,
|
||||||
|
info.config.vhostHTTPSPort,
|
||||||
|
info.config.subdomainHost,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (type === 'tcpmux') {
|
if (type === 'tcpmux') {
|
||||||
const info = await fetchServerInfo()
|
const info = await fetchServerInfo()
|
||||||
if (info && info.tcpmuxHTTPConnectPort) {
|
if (info && info.config.tcpmuxHTTPConnectPort) {
|
||||||
return new TCPMuxProxy(
|
return new TCPMuxProxy(
|
||||||
proxy,
|
proxy,
|
||||||
info.tcpmuxHTTPConnectPort,
|
info.config.tcpmuxHTTPConnectPort,
|
||||||
info.subdomainHost,
|
info.config.subdomainHost,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ import {
|
|||||||
} from '../api/proxy'
|
} from '../api/proxy'
|
||||||
import { getServerInfo } from '../api/server'
|
import { getServerInfo } from '../api/server'
|
||||||
import type { ProxyStatsInfo } from '../types/proxy'
|
import type { ProxyStatsInfo } from '../types/proxy'
|
||||||
|
import type { ServerInfo } from '../types/server'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -133,15 +134,9 @@ let searchDebounceTimer: number | null = null
|
|||||||
|
|
||||||
// Server info cache - cache the Promise itself so concurrent first calls
|
// Server info cache - cache the Promise itself so concurrent first calls
|
||||||
// from Promise.all (convertProxies) don't kick off multiple HTTP requests.
|
// from Promise.all (convertProxies) don't kick off multiple HTTP requests.
|
||||||
type ServerInfoLite = {
|
let serverInfoPromise: Promise<ServerInfo> | null = null
|
||||||
vhostHTTPPort: number
|
|
||||||
vhostHTTPSPort: number
|
|
||||||
tcpmuxHTTPConnectPort: number
|
|
||||||
subdomainHost: string
|
|
||||||
}
|
|
||||||
let serverInfoPromise: Promise<ServerInfoLite> | null = null
|
|
||||||
|
|
||||||
const fetchServerInfo = (): Promise<ServerInfoLite> => {
|
const fetchServerInfo = (): Promise<ServerInfo> => {
|
||||||
if (!serverInfoPromise) {
|
if (!serverInfoPromise) {
|
||||||
serverInfoPromise = getServerInfo().catch((err) => {
|
serverInfoPromise = getServerInfo().catch((err) => {
|
||||||
// Allow retry after failure
|
// Allow retry after failure
|
||||||
@@ -164,25 +159,33 @@ const convertProxy = async (
|
|||||||
}
|
}
|
||||||
if (type === 'http') {
|
if (type === 'http') {
|
||||||
const info = await fetchServerInfo()
|
const info = await fetchServerInfo()
|
||||||
if (info && info.vhostHTTPPort) {
|
if (info && info.config.vhostHTTPPort) {
|
||||||
return new HTTPProxy(proxy, info.vhostHTTPPort, info.subdomainHost)
|
return new HTTPProxy(
|
||||||
|
proxy,
|
||||||
|
info.config.vhostHTTPPort,
|
||||||
|
info.config.subdomainHost,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (type === 'https') {
|
if (type === 'https') {
|
||||||
const info = await fetchServerInfo()
|
const info = await fetchServerInfo()
|
||||||
if (info && info.vhostHTTPSPort) {
|
if (info && info.config.vhostHTTPSPort) {
|
||||||
return new HTTPSProxy(proxy, info.vhostHTTPSPort, info.subdomainHost)
|
return new HTTPSProxy(
|
||||||
|
proxy,
|
||||||
|
info.config.vhostHTTPSPort,
|
||||||
|
info.config.subdomainHost,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (type === 'tcpmux') {
|
if (type === 'tcpmux') {
|
||||||
const info = await fetchServerInfo()
|
const info = await fetchServerInfo()
|
||||||
if (info && info.tcpmuxHTTPConnectPort) {
|
if (info && info.config.tcpmuxHTTPConnectPort) {
|
||||||
return new TCPMuxProxy(
|
return new TCPMuxProxy(
|
||||||
proxy,
|
proxy,
|
||||||
info.tcpmuxHTTPConnectPort,
|
info.config.tcpmuxHTTPConnectPort,
|
||||||
info.subdomainHost,
|
info.config.subdomainHost,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ import {
|
|||||||
SUDPProxy,
|
SUDPProxy,
|
||||||
} from '../utils/proxy'
|
} from '../utils/proxy'
|
||||||
import Traffic from '../components/Traffic.vue'
|
import Traffic from '../components/Traffic.vue'
|
||||||
|
import type { ServerInfo } from '../types/server'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -275,12 +276,7 @@ const goBack = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let serverInfo: {
|
let serverInfo: ServerInfo | null = null
|
||||||
vhostHTTPPort: number
|
|
||||||
vhostHTTPSPort: number
|
|
||||||
tcpmuxHTTPConnectPort: number
|
|
||||||
subdomainHost: string
|
|
||||||
} | null = null
|
|
||||||
|
|
||||||
const clientLink = computed(() => {
|
const clientLink = computed(() => {
|
||||||
if (!proxy.value) return ''
|
if (!proxy.value) return ''
|
||||||
@@ -367,25 +363,30 @@ const fetchProxy = async () => {
|
|||||||
try {
|
try {
|
||||||
const data = await getProxyByNameV2(name)
|
const data = await getProxyByNameV2(name)
|
||||||
const info = await fetchServerInfo()
|
const info = await fetchServerInfo()
|
||||||
|
const config = info.config
|
||||||
const type = data.type || data.conf?.type || ''
|
const type = data.type || data.conf?.type || ''
|
||||||
|
|
||||||
if (type === 'tcp') {
|
if (type === 'tcp') {
|
||||||
proxy.value = new TCPProxy(data)
|
proxy.value = new TCPProxy(data)
|
||||||
} else if (type === 'udp') {
|
} else if (type === 'udp') {
|
||||||
proxy.value = new UDPProxy(data)
|
proxy.value = new UDPProxy(data)
|
||||||
} else if (type === 'http' && info?.vhostHTTPPort) {
|
} else if (type === 'http' && config.vhostHTTPPort) {
|
||||||
proxy.value = new HTTPProxy(data, info.vhostHTTPPort, info.subdomainHost)
|
proxy.value = new HTTPProxy(
|
||||||
} else if (type === 'https' && info?.vhostHTTPSPort) {
|
data,
|
||||||
|
config.vhostHTTPPort,
|
||||||
|
config.subdomainHost,
|
||||||
|
)
|
||||||
|
} else if (type === 'https' && config.vhostHTTPSPort) {
|
||||||
proxy.value = new HTTPSProxy(
|
proxy.value = new HTTPSProxy(
|
||||||
data,
|
data,
|
||||||
info.vhostHTTPSPort,
|
config.vhostHTTPSPort,
|
||||||
info.subdomainHost,
|
config.subdomainHost,
|
||||||
)
|
)
|
||||||
} else if (type === 'tcpmux' && info?.tcpmuxHTTPConnectPort) {
|
} else if (type === 'tcpmux' && config.tcpmuxHTTPConnectPort) {
|
||||||
proxy.value = new TCPMuxProxy(
|
proxy.value = new TCPMuxProxy(
|
||||||
data,
|
data,
|
||||||
info.tcpmuxHTTPConnectPort,
|
config.tcpmuxHTTPConnectPort,
|
||||||
info.subdomainHost,
|
config.subdomainHost,
|
||||||
)
|
)
|
||||||
} else if (type === 'stcp') {
|
} else if (type === 'stcp') {
|
||||||
proxy.value = new STCPProxy(data)
|
proxy.value = new STCPProxy(data)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<el-col :xs="24" :sm="12" :lg="6">
|
<el-col :xs="24" :sm="12" :lg="6">
|
||||||
<StatCard
|
<StatCard
|
||||||
label="Clients"
|
label="Clients"
|
||||||
:value="data.clientCounts"
|
:value="data.status.clientCounts"
|
||||||
type="clients"
|
type="clients"
|
||||||
subtitle="Connected clients"
|
subtitle="Connected clients"
|
||||||
to="/clients"
|
to="/clients"
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<el-col :xs="24" :sm="12" :lg="6">
|
<el-col :xs="24" :sm="12" :lg="6">
|
||||||
<StatCard
|
<StatCard
|
||||||
label="Proxies"
|
label="Proxies"
|
||||||
:value="data.proxyCounts"
|
:value="proxyCounts"
|
||||||
type="proxies"
|
type="proxies"
|
||||||
subtitle="Active proxies"
|
subtitle="Active proxies"
|
||||||
to="/proxies/tcp"
|
to="/proxies/tcp"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<el-col :xs="24" :sm="12" :lg="6">
|
<el-col :xs="24" :sm="12" :lg="6">
|
||||||
<StatCard
|
<StatCard
|
||||||
label="Connections"
|
label="Connections"
|
||||||
:value="data.curConns"
|
:value="data.status.curConns"
|
||||||
type="connections"
|
type="connections"
|
||||||
subtitle="Current connections"
|
subtitle="Current connections"
|
||||||
/>
|
/>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
<div class="traffic-info">
|
<div class="traffic-info">
|
||||||
<div class="label">Inbound</div>
|
<div class="label">Inbound</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
{{ formatFileSize(data.totalTrafficIn) }}
|
{{ formatFileSize(data.status.totalTrafficIn) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
<div class="traffic-info">
|
<div class="traffic-info">
|
||||||
<div class="label">Outbound</div>
|
<div class="label">Outbound</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
{{ formatFileSize(data.totalTrafficOut) }}
|
{{ formatFileSize(data.status.totalTrafficOut) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<div class="proxy-types-grid">
|
<div class="proxy-types-grid">
|
||||||
<div
|
<div
|
||||||
v-for="(count, type) in data.proxyTypeCounts"
|
v-for="(count, type) in data.status.proxyTypeCount"
|
||||||
:key="type"
|
:key="type"
|
||||||
class="proxy-type-item"
|
class="proxy-type-item"
|
||||||
v-show="count > 0"
|
v-show="count > 0"
|
||||||
@@ -109,51 +109,51 @@
|
|||||||
<div class="config-grid">
|
<div class="config-grid">
|
||||||
<div class="config-item">
|
<div class="config-item">
|
||||||
<span class="config-label">Bind Port</span>
|
<span class="config-label">Bind Port</span>
|
||||||
<span class="config-value">{{ data.bindPort }}</span>
|
<span class="config-value">{{ data.config.bindPort }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item" v-if="data.kcpBindPort != 0">
|
<div class="config-item" v-if="data.config.kcpBindPort != 0">
|
||||||
<span class="config-label">KCP Port</span>
|
<span class="config-label">KCP Port</span>
|
||||||
<span class="config-value">{{ data.kcpBindPort }}</span>
|
<span class="config-value">{{ data.config.kcpBindPort }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item" v-if="data.quicBindPort != 0">
|
<div class="config-item" v-if="data.config.quicBindPort != 0">
|
||||||
<span class="config-label">QUIC Port</span>
|
<span class="config-label">QUIC Port</span>
|
||||||
<span class="config-value">{{ data.quicBindPort }}</span>
|
<span class="config-value">{{ data.config.quicBindPort }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item" v-if="data.vhostHTTPPort != 0">
|
<div class="config-item" v-if="data.config.vhostHTTPPort != 0">
|
||||||
<span class="config-label">HTTP Port</span>
|
<span class="config-label">HTTP Port</span>
|
||||||
<span class="config-value">{{ data.vhostHTTPPort }}</span>
|
<span class="config-value">{{ data.config.vhostHTTPPort }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item" v-if="data.vhostHTTPSPort != 0">
|
<div class="config-item" v-if="data.config.vhostHTTPSPort != 0">
|
||||||
<span class="config-label">HTTPS Port</span>
|
<span class="config-label">HTTPS Port</span>
|
||||||
<span class="config-value">{{ data.vhostHTTPSPort }}</span>
|
<span class="config-value">{{ data.config.vhostHTTPSPort }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item" v-if="data.tcpmuxHTTPConnectPort != 0">
|
<div class="config-item" v-if="data.config.tcpmuxHTTPConnectPort != 0">
|
||||||
<span class="config-label">TCPMux Port</span>
|
<span class="config-label">TCPMux Port</span>
|
||||||
<span class="config-value">{{ data.tcpmuxHTTPConnectPort }}</span>
|
<span class="config-value">{{ data.config.tcpmuxHTTPConnectPort }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item" v-if="data.subdomainHost != ''">
|
<div class="config-item" v-if="data.config.subdomainHost != ''">
|
||||||
<span class="config-label">Subdomain Host</span>
|
<span class="config-label">Subdomain Host</span>
|
||||||
<span class="config-value">{{ data.subdomainHost }}</span>
|
<span class="config-value">{{ data.config.subdomainHost }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item">
|
<div class="config-item">
|
||||||
<span class="config-label">Max Pool Count</span>
|
<span class="config-label">Max Pool Count</span>
|
||||||
<span class="config-value">{{ data.maxPoolCount }}</span>
|
<span class="config-value">{{ data.config.maxPoolCount }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item">
|
<div class="config-item">
|
||||||
<span class="config-label">Max Ports/Client</span>
|
<span class="config-label">Max Ports/Client</span>
|
||||||
<span class="config-value">{{ data.maxPortsPerClient }}</span>
|
<span class="config-value">{{ maxPortsPerClientLabel }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item" v-if="data.allowPortsStr != ''">
|
<div class="config-item" v-if="data.config.allowPortsStr != ''">
|
||||||
<span class="config-label">Allow Ports</span>
|
<span class="config-label">Allow Ports</span>
|
||||||
<span class="config-value">{{ data.allowPortsStr }}</span>
|
<span class="config-value">{{ data.config.allowPortsStr }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item" v-if="data.tlsForce">
|
<div class="config-item" v-if="data.config.tlsForce">
|
||||||
<span class="config-label">TLS Force</span>
|
<span class="config-label">TLS Force</span>
|
||||||
<el-tag size="small" type="warning">Enabled</el-tag>
|
<el-tag size="small" type="warning">Enabled</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item">
|
<div class="config-item">
|
||||||
<span class="config-label">Heartbeat Timeout</span>
|
<span class="config-label">Heartbeat Timeout</span>
|
||||||
<span class="config-value">{{ data.heartbeatTimeout }}s</span>
|
<span class="config-value">{{ data.config.heartbeatTimeout }}s</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -167,69 +167,59 @@ import { formatFileSize } from '../utils/format'
|
|||||||
import { Download, Upload } from '@element-plus/icons-vue'
|
import { Download, Upload } from '@element-plus/icons-vue'
|
||||||
import StatCard from '../components/StatCard.vue'
|
import StatCard from '../components/StatCard.vue'
|
||||||
import { getServerInfo } from '../api/server'
|
import { getServerInfo } from '../api/server'
|
||||||
|
import type { ServerInfo } from '../types/server'
|
||||||
|
|
||||||
const data = ref({
|
const data = ref<ServerInfo>({
|
||||||
version: '',
|
version: '',
|
||||||
bindPort: 0,
|
config: {
|
||||||
kcpBindPort: 0,
|
bindPort: 0,
|
||||||
quicBindPort: 0,
|
kcpBindPort: 0,
|
||||||
vhostHTTPPort: 0,
|
quicBindPort: 0,
|
||||||
vhostHTTPSPort: 0,
|
vhostHTTPPort: 0,
|
||||||
tcpmuxHTTPConnectPort: 0,
|
vhostHTTPSPort: 0,
|
||||||
subdomainHost: '',
|
tcpmuxHTTPConnectPort: 0,
|
||||||
maxPoolCount: 0,
|
subdomainHost: '',
|
||||||
maxPortsPerClient: '',
|
maxPoolCount: 0,
|
||||||
allowPortsStr: '',
|
maxPortsPerClient: 0,
|
||||||
tlsForce: false,
|
allowPortsStr: '',
|
||||||
heartbeatTimeout: 0,
|
tlsForce: false,
|
||||||
clientCounts: 0,
|
heartbeatTimeout: 0,
|
||||||
curConns: 0,
|
},
|
||||||
proxyCounts: 0,
|
status: {
|
||||||
totalTrafficIn: 0,
|
clientCounts: 0,
|
||||||
totalTrafficOut: 0,
|
curConns: 0,
|
||||||
proxyTypeCounts: {} as Record<string, number>,
|
totalTrafficIn: 0,
|
||||||
|
totalTrafficOut: 0,
|
||||||
|
proxyTypeCount: {},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const hasActiveProxies = computed(() => {
|
const hasActiveProxies = computed(() => {
|
||||||
return Object.values(data.value.proxyTypeCounts).some((c) => c > 0)
|
return Object.values(data.value.status.proxyTypeCount).some((c) => c > 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const proxyCounts = computed(() => {
|
||||||
|
return Object.values(data.value.status.proxyTypeCount).reduce(
|
||||||
|
(sum, count) => sum + (count || 0),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const maxPortsPerClientLabel = computed(() => {
|
||||||
|
const value = data.value.config.maxPortsPerClient
|
||||||
|
return value === 0 ? 'no limit' : String(value)
|
||||||
})
|
})
|
||||||
|
|
||||||
const formatTrafficTotal = () => {
|
const formatTrafficTotal = () => {
|
||||||
const total = data.value.totalTrafficIn + data.value.totalTrafficOut
|
const total =
|
||||||
|
data.value.status.totalTrafficIn + data.value.status.totalTrafficOut
|
||||||
return formatFileSize(total)
|
return formatFileSize(total)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const json = await getServerInfo()
|
const json = await getServerInfo()
|
||||||
data.value.version = json.version
|
data.value = json
|
||||||
data.value.bindPort = json.bindPort
|
|
||||||
data.value.kcpBindPort = json.kcpBindPort
|
|
||||||
data.value.quicBindPort = json.quicBindPort
|
|
||||||
data.value.vhostHTTPPort = json.vhostHTTPPort
|
|
||||||
data.value.vhostHTTPSPort = json.vhostHTTPSPort
|
|
||||||
data.value.tcpmuxHTTPConnectPort = json.tcpmuxHTTPConnectPort
|
|
||||||
data.value.subdomainHost = json.subdomainHost
|
|
||||||
data.value.maxPoolCount = json.maxPoolCount
|
|
||||||
data.value.maxPortsPerClient = String(json.maxPortsPerClient)
|
|
||||||
if (data.value.maxPortsPerClient == '0') {
|
|
||||||
data.value.maxPortsPerClient = 'no limit'
|
|
||||||
}
|
|
||||||
data.value.allowPortsStr = json.allowPortsStr
|
|
||||||
data.value.tlsForce = json.tlsForce
|
|
||||||
data.value.heartbeatTimeout = json.heartbeatTimeout
|
|
||||||
data.value.clientCounts = json.clientCounts
|
|
||||||
data.value.curConns = json.curConns
|
|
||||||
data.value.totalTrafficIn = json.totalTrafficIn
|
|
||||||
data.value.totalTrafficOut = json.totalTrafficOut
|
|
||||||
data.value.proxyTypeCounts = json.proxyTypeCount || {}
|
|
||||||
|
|
||||||
data.value.proxyCounts = 0
|
|
||||||
if (json.proxyTypeCount != null) {
|
|
||||||
Object.values(json.proxyTypeCount).forEach((count: any) => {
|
|
||||||
data.value.proxyCounts += count || 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
showClose: true,
|
showClose: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user