mirror of
https://github.com/VaalaCat/frp-panel.git
synced 2025-09-27 03:36:10 +08:00
feat: support client plugin and proxy can be stopped
feat: i18n parser i18n: translate feat: add github proxy url
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -185,7 +184,7 @@ func UpdateFrpcHander(c *app.Context, req *pb.UpdateFRPCRequest) (*pb.UpdateFRPC
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
childCtx := app.NewContext(context.Background(), c.GetApp())
|
childCtx := c.Background()
|
||||||
cliToUpdate, err := dao.NewQuery(childCtx).GetClientByFilter(userInfo, &models.ClientEntity{ClientID: cli.OriginClientID}, nil)
|
cliToUpdate, err := dao.NewQuery(childCtx).GetClientByFilter(userInfo, &models.ClientEntity{ClientID: cli.OriginClientID}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logger(childCtx).WithError(err).Errorf("cannot get origin client, id: [%s]", cliToUpdate.OriginClientID)
|
logger.Logger(childCtx).WithError(err).Errorf("cannot get origin client, id: [%s]", cliToUpdate.OriginClientID)
|
||||||
|
@@ -80,6 +80,8 @@ func ConfigureRouter(appInstance app.Application, router *gin.Engine) {
|
|||||||
proxyRouter.POST("/update_config", app.Wrapper(appInstance, proxy.UpdateProxyConfig))
|
proxyRouter.POST("/update_config", app.Wrapper(appInstance, proxy.UpdateProxyConfig))
|
||||||
proxyRouter.POST("/delete_config", app.Wrapper(appInstance, proxy.DeleteProxyConfig))
|
proxyRouter.POST("/delete_config", app.Wrapper(appInstance, proxy.DeleteProxyConfig))
|
||||||
proxyRouter.POST("/get_config", app.Wrapper(appInstance, proxy.GetProxyConfig))
|
proxyRouter.POST("/get_config", app.Wrapper(appInstance, proxy.GetProxyConfig))
|
||||||
|
proxyRouter.POST("/start_proxy", app.Wrapper(appInstance, proxy.StartProxy))
|
||||||
|
proxyRouter.POST("/stop_proxy", app.Wrapper(appInstance, proxy.StopProxy))
|
||||||
}
|
}
|
||||||
v1.GET("/pty/:clientID", shell.PTYHandler(appInstance))
|
v1.GET("/pty/:clientID", shell.PTYHandler(appInstance))
|
||||||
v1.GET("/log", streamlog.GetLogHandler(appInstance))
|
v1.GET("/log", streamlog.GetLogHandler(appInstance))
|
||||||
|
@@ -76,5 +76,6 @@ func getPlatformInfo(appInstance app.Application, c *gin.Context) (*pb.GetPlatfo
|
|||||||
MasterApiScheme: appInstance.GetConfig().Master.APIScheme,
|
MasterApiScheme: appInstance.GetConfig().Master.APIScheme,
|
||||||
ClientRpcUrl: clientRPCUrl,
|
ClientRpcUrl: clientRPCUrl,
|
||||||
ClientApiUrl: clientAPIUrl,
|
ClientApiUrl: clientAPIUrl,
|
||||||
|
GithubProxyUrl: appInstance.GetConfig().App.GithubProxyUrl,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@@ -30,32 +30,7 @@ func CreateProxyConfig(c *app.Context, req *pb.CreateProxyConfigRequest) (*pb.Cr
|
|||||||
serverID = req.GetServerId()
|
serverID = req.GetServerId()
|
||||||
)
|
)
|
||||||
|
|
||||||
// 1. 检查是否有已连接该服务端的客户端
|
clientEntity, err := getClientWithMakeShadow(c, clientID, serverID)
|
||||||
// 2. 检查是否有Shadow客户端
|
|
||||||
// 3. 如果没有,则新建Shadow客户端和子客户端
|
|
||||||
clientEntity, err := dao.NewQuery(c).GetClientByFilter(userInfo, &models.ClientEntity{OriginClientID: clientID, ServerID: serverID}, lo.ToPtr(false))
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
clientEntity, err = dao.NewQuery(c).GetClientByFilter(userInfo, &models.ClientEntity{ClientID: clientID}, nil)
|
|
||||||
if err != nil {
|
|
||||||
logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", clientID)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if (!clientEntity.IsShadow || len(clientEntity.ConfigContent) != 0) && len(clientEntity.OriginClientID) == 0 {
|
|
||||||
// 没shadow过,需要shadow
|
|
||||||
_, err = client.MakeClientShadowed(c, serverID, clientEntity)
|
|
||||||
if err != nil {
|
|
||||||
logger.Logger(c).WithError(err).Errorf("cannot make client shadow, id: [%s]", clientID)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// shadow过,但没找到子客户端,需要新建
|
|
||||||
clientEntity, err = client.ChildClientForServer(c, serverID, clientEntity)
|
|
||||||
if err != nil {
|
|
||||||
logger.Logger(c).WithError(err).Errorf("cannot create child client, id: [%s]", clientID)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 有任何失败,返回
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", clientID)
|
logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", clientID)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -128,6 +103,7 @@ func CreateProxyConfig(c *app.Context, req *pb.CreateProxyConfigRequest) (*pb.Cr
|
|||||||
ServerId: &serverID,
|
ServerId: &serverID,
|
||||||
Config: rawCfg,
|
Config: rawCfg,
|
||||||
Comment: &clientEntity.Comment,
|
Comment: &clientEntity.Comment,
|
||||||
|
FrpsUrl: &clientEntity.FrpsUrl,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logger(c).WithError(err).Warnf("cannot update frpc failed, id: [%s]", clientID)
|
logger.Logger(c).WithError(err).Warnf("cannot update frpc failed, id: [%s]", clientID)
|
||||||
|
@@ -65,6 +65,7 @@ func DeleteProxyConfig(c *app.Context, req *pb.DeleteProxyConfigRequest) (*pb.De
|
|||||||
ServerId: &serverID,
|
ServerId: &serverID,
|
||||||
Config: rawCfg,
|
Config: rawCfg,
|
||||||
Comment: &cli.Comment,
|
Comment: &cli.Comment,
|
||||||
|
FrpsUrl: &cli.FrpsUrl,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logger(c).WithError(err).Errorf("cannot update frpc, id: [%s]", clientID)
|
logger.Logger(c).WithError(err).Errorf("cannot update frpc, id: [%s]", clientID)
|
||||||
|
@@ -31,21 +31,25 @@ func GetProxyConfig(c *app.Context, req *pb.GetProxyConfigRequest) (*pb.GetProxy
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := &pb.GetProxyConfigResponse{}
|
resp := &pb.GetProxyConfigResponse{WorkingStatus: &pb.ProxyWorkingStatus{
|
||||||
if err := rpc.CallClientWrapper(c, proxyConfig.OriginClientID, pb.Event_EVENT_GET_PROXY_INFO, &pb.GetProxyConfigRequest{
|
Status: lo.ToPtr("stopped"),
|
||||||
ClientId: lo.ToPtr(proxyConfig.ClientID),
|
}}
|
||||||
ServerId: lo.ToPtr(proxyConfig.ServerID),
|
if !proxyConfig.Stopped {
|
||||||
Name: lo.ToPtr(fmt.Sprintf("%s.%s", userInfo.GetUserName(), proxyName)),
|
if err := rpc.CallClientWrapper(c, proxyConfig.OriginClientID, pb.Event_EVENT_GET_PROXY_INFO, &pb.GetProxyConfigRequest{
|
||||||
}, resp); err != nil {
|
ClientId: lo.ToPtr(proxyConfig.ClientID),
|
||||||
resp.WorkingStatus = &pb.ProxyWorkingStatus{
|
ServerId: lo.ToPtr(proxyConfig.ServerID),
|
||||||
Status: lo.ToPtr("error"),
|
Name: lo.ToPtr(fmt.Sprintf("%s.%s", userInfo.GetUserName(), proxyName)),
|
||||||
|
}, resp); err != nil {
|
||||||
|
resp.WorkingStatus = &pb.ProxyWorkingStatus{
|
||||||
|
Status: lo.ToPtr("error"),
|
||||||
|
}
|
||||||
|
logger.Logger(c).WithError(err).Errorf("cannot get proxy config, client: [%s], server: [%s], proxy name: [%s]", proxyConfig.OriginClientID, proxyConfig.ServerID, proxyConfig.Name)
|
||||||
}
|
}
|
||||||
logger.Logger(c).WithError(err).Errorf("cannot get proxy config, client: [%s], server: [%s], proxy name: [%s]", proxyConfig.OriginClientID, proxyConfig.ServerID, proxyConfig.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resp.GetWorkingStatus().GetStatus()) == 0 {
|
if len(resp.GetWorkingStatus().GetStatus()) == 0 {
|
||||||
resp.WorkingStatus = &pb.ProxyWorkingStatus{
|
resp.WorkingStatus = &pb.ProxyWorkingStatus{
|
||||||
Status: lo.ToPtr("unknown"),
|
Status: lo.ToPtr("unknown"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +63,7 @@ func GetProxyConfig(c *app.Context, req *pb.GetProxyConfigRequest) (*pb.GetProxy
|
|||||||
ServerId: lo.ToPtr(proxyConfig.ServerID),
|
ServerId: lo.ToPtr(proxyConfig.ServerID),
|
||||||
Config: lo.ToPtr(string(proxyConfig.Content)),
|
Config: lo.ToPtr(string(proxyConfig.Content)),
|
||||||
OriginClientId: lo.ToPtr(proxyConfig.OriginClientID),
|
OriginClientId: lo.ToPtr(proxyConfig.OriginClientID),
|
||||||
|
Stopped: lo.ToPtr(proxyConfig.Stopped),
|
||||||
},
|
},
|
||||||
WorkingStatus: resp.GetWorkingStatus(),
|
WorkingStatus: resp.GetWorkingStatus(),
|
||||||
}, nil
|
}, nil
|
||||||
|
@@ -1,9 +1,17 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/VaalaCat/frp-panel/biz/master/client"
|
||||||
|
"github.com/VaalaCat/frp-panel/common"
|
||||||
"github.com/VaalaCat/frp-panel/models"
|
"github.com/VaalaCat/frp-panel/models"
|
||||||
"github.com/VaalaCat/frp-panel/pb"
|
"github.com/VaalaCat/frp-panel/pb"
|
||||||
|
"github.com/VaalaCat/frp-panel/services/app"
|
||||||
|
"github.com/VaalaCat/frp-panel/services/dao"
|
||||||
|
"github.com/VaalaCat/frp-panel/utils/logger"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func convertProxyStatsList(proxyList []*models.ProxyStatsEntity) []*pb.ProxyInfo {
|
func convertProxyStatsList(proxyList []*models.ProxyStatsEntity) []*pb.ProxyInfo {
|
||||||
@@ -20,3 +28,40 @@ func convertProxyStatsList(proxyList []*models.ProxyStatsEntity) []*pb.ProxyInfo
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getClientWithMakeShadow
|
||||||
|
// 1. 检查是否有已连接该服务端的客户端
|
||||||
|
// 2. 检查是否有Shadow客户端
|
||||||
|
// 3. 如果没有,则新建Shadow客户端和子客户端
|
||||||
|
func getClientWithMakeShadow(c *app.Context, clientID, serverID string) (*models.ClientEntity, error) {
|
||||||
|
userInfo := common.GetUserInfo(c)
|
||||||
|
clientEntity, err := dao.NewQuery(c).GetClientByFilter(userInfo, &models.ClientEntity{OriginClientID: clientID, ServerID: serverID}, lo.ToPtr(false))
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
clientEntity, err = dao.NewQuery(c).GetClientByFilter(userInfo, &models.ClientEntity{ClientID: clientID}, nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", clientID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if (!clientEntity.IsShadow || len(clientEntity.ConfigContent) != 0) && len(clientEntity.OriginClientID) == 0 {
|
||||||
|
// 没shadow过,需要shadow
|
||||||
|
_, err = client.MakeClientShadowed(c, serverID, clientEntity)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(c).WithError(err).Errorf("cannot make client shadow, id: [%s]", clientID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// shadow过,但没找到子客户端,需要新建
|
||||||
|
clientEntity, err = client.ChildClientForServer(c, serverID, clientEntity)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(c).WithError(err).Errorf("cannot create child client, id: [%s]", clientID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 有任何失败,返回
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(c).WithError(err).Errorf("cannot get client, id: [%s]", clientID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientEntity, nil
|
||||||
|
}
|
||||||
|
@@ -74,6 +74,7 @@ func ListProxyConfigs(ctx *app.Context, req *pb.ListProxyConfigsRequest) (*pb.Li
|
|||||||
ServerId: lo.ToPtr(item.ServerID),
|
ServerId: lo.ToPtr(item.ServerID),
|
||||||
Config: lo.ToPtr(string(item.Content)),
|
Config: lo.ToPtr(string(item.Content)),
|
||||||
OriginClientId: lo.ToPtr(item.OriginClientID),
|
OriginClientId: lo.ToPtr(item.OriginClientID),
|
||||||
|
Stopped: lo.ToPtr(item.Stopped),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
100
biz/master/proxy/start_proxy.go
Normal file
100
biz/master/proxy/start_proxy.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/VaalaCat/frp-panel/biz/master/client"
|
||||||
|
"github.com/VaalaCat/frp-panel/common"
|
||||||
|
"github.com/VaalaCat/frp-panel/models"
|
||||||
|
"github.com/VaalaCat/frp-panel/pb"
|
||||||
|
"github.com/VaalaCat/frp-panel/services/app"
|
||||||
|
"github.com/VaalaCat/frp-panel/services/dao"
|
||||||
|
"github.com/VaalaCat/frp-panel/utils/logger"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StartProxy(ctx *app.Context, req *pb.StartProxyRequest) (*pb.StartProxyResponse, error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
userInfo = common.GetUserInfo(ctx)
|
||||||
|
clientID = req.GetClientId()
|
||||||
|
serverID = req.GetServerId()
|
||||||
|
proxyName = req.GetName()
|
||||||
|
)
|
||||||
|
|
||||||
|
clientEntity, err := getClientWithMakeShadow(ctx, clientID, serverID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot get client, id: [%s]", clientID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = dao.NewQuery(ctx).GetServerByServerID(userInfo, serverID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot get server, id: [%s]", serverID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyConfig, err := dao.NewQuery(ctx).GetProxyConfigByFilter(userInfo, &models.ProxyConfigEntity{
|
||||||
|
ClientID: clientID,
|
||||||
|
ServerID: serverID,
|
||||||
|
Name: proxyName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot get proxy config, client: [%s], server: [%s], proxy name: [%s]", clientID, serverID, proxyName)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 更新proxy状态
|
||||||
|
proxyConfig.Stopped = false
|
||||||
|
err = dao.NewQuery(ctx).UpdateProxyConfig(userInfo, proxyConfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot update proxy config, client: [%s], server: [%s], proxy name: [%s]", clientID, serverID, proxyName)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
typedProxyConfig, err := proxyConfig.GetTypedProxyConfig()
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot get typed proxy config, client: [%s], server: [%s], proxy name: [%s]", clientID, serverID, proxyName)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 添加 proxy到client
|
||||||
|
if oldCfg, err := clientEntity.GetConfigContent(); err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot get client config, id: [%s]", clientID)
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
oldCfg.Proxies = lo.Filter(oldCfg.Proxies, func(proxy v1.TypedProxyConfig, _ int) bool {
|
||||||
|
return proxy.GetBaseConfig().Name != proxyName
|
||||||
|
})
|
||||||
|
oldCfg.Proxies = append(oldCfg.Proxies, typedProxyConfig)
|
||||||
|
|
||||||
|
if err := clientEntity.SetConfigContent(*oldCfg); err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot set client config, id: [%s]", clientID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 更新client的配置
|
||||||
|
rawCfg, err := clientEntity.MarshalJSONConfig()
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot marshal client config, id: [%s]", clientID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.UpdateFrpcHander(ctx, &pb.UpdateFRPCRequest{
|
||||||
|
ClientId: &clientEntity.ClientID,
|
||||||
|
ServerId: &serverID,
|
||||||
|
Config: rawCfg,
|
||||||
|
Comment: &clientEntity.Comment,
|
||||||
|
FrpsUrl: &clientEntity.FrpsUrl,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Warnf("cannot update frpc, id: [%s]", clientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.StartProxyResponse{
|
||||||
|
Status: &pb.Status{
|
||||||
|
Code: pb.RespCode_RESP_CODE_SUCCESS,
|
||||||
|
Message: "start proxy success",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
92
biz/master/proxy/stop_proxy.go
Normal file
92
biz/master/proxy/stop_proxy.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/VaalaCat/frp-panel/biz/master/client"
|
||||||
|
"github.com/VaalaCat/frp-panel/common"
|
||||||
|
"github.com/VaalaCat/frp-panel/models"
|
||||||
|
"github.com/VaalaCat/frp-panel/pb"
|
||||||
|
"github.com/VaalaCat/frp-panel/services/app"
|
||||||
|
"github.com/VaalaCat/frp-panel/services/dao"
|
||||||
|
"github.com/VaalaCat/frp-panel/utils/logger"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StopProxy(ctx *app.Context, req *pb.StopProxyRequest) (*pb.StopProxyResponse, error) {
|
||||||
|
var (
|
||||||
|
userInfo = common.GetUserInfo(ctx)
|
||||||
|
clientID = req.GetClientId()
|
||||||
|
serverID = req.GetServerId()
|
||||||
|
proxyName = req.GetName()
|
||||||
|
)
|
||||||
|
|
||||||
|
clientEntity, err := getClientWithMakeShadow(ctx, clientID, serverID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot get client, id: [%s]", clientID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = dao.NewQuery(ctx).GetServerByServerID(userInfo, serverID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot get server, id: [%s]", serverID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyConfig, err := dao.NewQuery(ctx).GetProxyConfigByFilter(userInfo, &models.ProxyConfigEntity{
|
||||||
|
ClientID: clientID,
|
||||||
|
ServerID: serverID,
|
||||||
|
Name: proxyName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot get proxy config, client: [%s], server: [%s], proxy name: [%s]", clientID, serverID, proxyName)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 更新proxy状态
|
||||||
|
proxyConfig.Stopped = true
|
||||||
|
err = dao.NewQuery(ctx).UpdateProxyConfig(userInfo, proxyConfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot update proxy config, client: [%s], server: [%s], proxy name: [%s]", clientID, serverID, proxyName)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 从client移除proxy
|
||||||
|
if oldCfg, err := clientEntity.GetConfigContent(); err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot get client config, id: [%s]", clientID)
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
oldCfg.Proxies = lo.Filter(oldCfg.Proxies, func(proxy v1.TypedProxyConfig, _ int) bool {
|
||||||
|
return proxy.GetBaseConfig().Name != proxyName
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := clientEntity.SetConfigContent(*oldCfg); err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot set client config, id: [%s]", clientID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 更新client的配置
|
||||||
|
rawCfg, err := clientEntity.MarshalJSONConfig()
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Errorf("cannot marshal client config, id: [%s]", clientID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.UpdateFrpcHander(ctx, &pb.UpdateFRPCRequest{
|
||||||
|
ClientId: &clientEntity.ClientID,
|
||||||
|
ServerId: &serverID,
|
||||||
|
Config: rawCfg,
|
||||||
|
Comment: &clientEntity.Comment,
|
||||||
|
FrpsUrl: &clientEntity.FrpsUrl,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger(ctx).WithError(err).Warnf("cannot update frpc, id: [%s]", clientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.StopProxyResponse{
|
||||||
|
Status: &pb.Status{
|
||||||
|
Code: pb.RespCode_RESP_CODE_SUCCESS,
|
||||||
|
Message: "stop proxy success",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
@@ -115,6 +115,7 @@ func UpdateProxyConfig(c *app.Context, req *pb.UpdateProxyConfigRequest) (*pb.Up
|
|||||||
ServerId: &serverID,
|
ServerId: &serverID,
|
||||||
Config: rawCfg,
|
Config: rawCfg,
|
||||||
Comment: &clientEntity.Comment,
|
Comment: &clientEntity.Comment,
|
||||||
|
FrpsUrl: &clientEntity.FrpsUrl,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logger(c).WithError(err).Errorf("cannot update frpc, id: [%s]", clientID)
|
logger.Logger(c).WithError(err).Errorf("cannot update frpc, id: [%s]", clientID)
|
||||||
|
@@ -24,7 +24,8 @@ type ReqType interface {
|
|||||||
pb.StartFRPCRequest | pb.StopFRPCRequest | pb.StartFRPSRequest | pb.StopFRPSRequest |
|
pb.StartFRPCRequest | pb.StopFRPCRequest | pb.StartFRPSRequest | pb.StopFRPSRequest |
|
||||||
pb.GetProxyStatsByClientIDRequest | pb.GetProxyStatsByServerIDRequest |
|
pb.GetProxyStatsByClientIDRequest | pb.GetProxyStatsByServerIDRequest |
|
||||||
pb.CreateProxyConfigRequest | pb.ListProxyConfigsRequest | pb.UpdateProxyConfigRequest |
|
pb.CreateProxyConfigRequest | pb.ListProxyConfigsRequest | pb.UpdateProxyConfigRequest |
|
||||||
pb.DeleteProxyConfigRequest | pb.GetProxyConfigRequest | pb.SignTokenRequest
|
pb.DeleteProxyConfigRequest | pb.GetProxyConfigRequest | pb.SignTokenRequest |
|
||||||
|
pb.StartProxyRequest | pb.StopProxyRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetProtoRequest[T ReqType](c *gin.Context) (r *T, err error) {
|
func GetProtoRequest[T ReqType](c *gin.Context) (r *T, err error) {
|
||||||
|
@@ -25,7 +25,8 @@ type RespType interface {
|
|||||||
pb.StartFRPCResponse | pb.StopFRPCResponse | pb.StartFRPSResponse | pb.StopFRPSResponse |
|
pb.StartFRPCResponse | pb.StopFRPCResponse | pb.StartFRPSResponse | pb.StopFRPSResponse |
|
||||||
pb.GetProxyStatsByClientIDResponse | pb.GetProxyStatsByServerIDResponse |
|
pb.GetProxyStatsByClientIDResponse | pb.GetProxyStatsByServerIDResponse |
|
||||||
pb.CreateProxyConfigResponse | pb.ListProxyConfigsResponse | pb.UpdateProxyConfigResponse |
|
pb.CreateProxyConfigResponse | pb.ListProxyConfigsResponse | pb.UpdateProxyConfigResponse |
|
||||||
pb.DeleteProxyConfigResponse | pb.GetProxyConfigResponse | pb.SignTokenResponse
|
pb.DeleteProxyConfigResponse | pb.GetProxyConfigResponse | pb.SignTokenResponse |
|
||||||
|
pb.StartProxyResponse | pb.StopProxyResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
func OKResp[T RespType](c *gin.Context, origin *T) {
|
func OKResp[T RespType](c *gin.Context, origin *T) {
|
||||||
|
@@ -24,6 +24,7 @@ type Config struct {
|
|||||||
CookieSecure bool `env:"COOKIE_SECURE" env-default:"false" env-description:"cookie secure"`
|
CookieSecure bool `env:"COOKIE_SECURE" env-default:"false" env-description:"cookie secure"`
|
||||||
CookieHTTPOnly bool `env:"COOKIE_HTTP_ONLY" env-default:"true" env-description:"cookie http only"`
|
CookieHTTPOnly bool `env:"COOKIE_HTTP_ONLY" env-default:"true" env-description:"cookie http only"`
|
||||||
EnableRegister bool `env:"ENABLE_REGISTER" env-default:"false" env-description:"enable register, only allow the first admin to register"`
|
EnableRegister bool `env:"ENABLE_REGISTER" env-default:"false" env-description:"enable register, only allow the first admin to register"`
|
||||||
|
GithubProxyUrl string `env:"GITHUB_PROXY_URL" env-default:"https://ghfast.top/" env-description:"github proxy url"`
|
||||||
} `env-prefix:"APP_"`
|
} `env-prefix:"APP_"`
|
||||||
Master struct {
|
Master struct {
|
||||||
APIPort int `env:"API_PORT" env-default:"9000" env-description:"master api port"`
|
APIPort int `env:"API_PORT" env-default:"9000" env-description:"master api port"`
|
||||||
|
@@ -6,7 +6,7 @@ Client 推荐使用 docker 部署,直接部署在客户机中,可以通过
|
|||||||
|
|
||||||
打开 Master 的 webui 并登录,如果没有账号,请直接注册,第一个用户即为管理员
|
打开 Master 的 webui 并登录,如果没有账号,请直接注册,第一个用户即为管理员
|
||||||
|
|
||||||
在侧边栏跳转到 `服务端`,点击上方的 `新建` 并输入 服务端 的唯一识别ID和 服务端 能够被公网访问的 IP/域名,然后点击保存
|
在侧边栏跳转到 `服务端`,点击上方的 `新建` 并输入 客户端 的唯一识别ID然后点击保存
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -59,4 +59,4 @@ services:
|
|||||||
network_mode: host
|
network_mode: host
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: client -s abc -i user.s.client1 -a 123123 -t frpp.example.com -r frpp-rpc.example.com -c 443 -p 443 -e https
|
command: client -s abc -i user.s.client1 -a 123123 -t frpp.example.com -r frpp-rpc.example.com -c 443 -p 443 -e https
|
||||||
```
|
```
|
||||||
|
@@ -147,3 +147,23 @@ message GetProxyConfigResponse {
|
|||||||
optional common.ProxyConfig proxy_config = 2;
|
optional common.ProxyConfig proxy_config = 2;
|
||||||
optional common.ProxyWorkingStatus working_status = 3;
|
optional common.ProxyWorkingStatus working_status = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message StopProxyRequest {
|
||||||
|
optional string client_id = 1;
|
||||||
|
optional string server_id = 2;
|
||||||
|
optional string name = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StopProxyResponse {
|
||||||
|
optional common.Status status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartProxyRequest {
|
||||||
|
optional string client_id = 1;
|
||||||
|
optional string server_id = 2;
|
||||||
|
optional string name = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartProxyResponse {
|
||||||
|
optional common.Status status = 1;
|
||||||
|
}
|
||||||
|
@@ -35,4 +35,5 @@ message GetPlatformInfoResponse {
|
|||||||
string master_api_scheme = 11;
|
string master_api_scheme = 11;
|
||||||
string client_rpc_url = 12;
|
string client_rpc_url = 12;
|
||||||
string client_api_url = 13;
|
string client_api_url = 13;
|
||||||
}
|
string github_proxy_url = 14;
|
||||||
|
}
|
||||||
|
@@ -87,6 +87,7 @@ message ProxyConfig {
|
|||||||
optional string server_id = 5;
|
optional string server_id = 5;
|
||||||
optional string config = 6;
|
optional string config = 6;
|
||||||
optional string origin_client_id = 7;
|
optional string origin_client_id = 7;
|
||||||
|
optional bool stopped = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ProxyWorkingStatus {
|
message ProxyWorkingStatus {
|
||||||
|
@@ -31,7 +31,7 @@ Write-Host "Check network connection to google" -BackgroundColor DarkGreen -Fore
|
|||||||
|
|
||||||
$networkAvailable = Test-Connection -ComputerName google.com -Count 1 -ErrorAction SilentlyContinue
|
$networkAvailable = Test-Connection -ComputerName google.com -Count 1 -ErrorAction SilentlyContinue
|
||||||
if([string]::IsNullOrEmpty($networkAvailable)){
|
if([string]::IsNullOrEmpty($networkAvailable)){
|
||||||
$download = "https://gh-proxy.com/https://github.com/$clientrepo/releases/latest/download/$file"
|
$download = "https://ghfast.top/https://github.com/$clientrepo/releases/latest/download/$file"
|
||||||
Write-Host "Location:CN,use mirror address" -BackgroundColor DarkRed -ForegroundColor Green
|
Write-Host "Location:CN,use mirror address" -BackgroundColor DarkRed -ForegroundColor Green
|
||||||
}else{
|
}else{
|
||||||
$download = "https://github.com/$clientrepo/releases/latest/download/$file"
|
$download = "https://github.com/$clientrepo/releases/latest/download/$file"
|
||||||
@@ -43,4 +43,4 @@ New-Item -Path "C:\frpp" -ItemType Directory -ErrorAction SilentlyContinue
|
|||||||
Move-Item -Path "C:\frpp.exe" -Destination "C:\frpp\frpp.exe"
|
Move-Item -Path "C:\frpp.exe" -Destination "C:\frpp\frpp.exe"
|
||||||
C:\frpp\frpp.exe install $args
|
C:\frpp\frpp.exe install $args
|
||||||
C:\frpp\frpp.exe start
|
C:\frpp\frpp.exe start
|
||||||
Write-Host "Enjoy It!" -BackgroundColor DarkGreen -ForegroundColor Red
|
Write-Host "Enjoy It!" -BackgroundColor DarkGreen -ForegroundColor Red
|
||||||
|
@@ -21,6 +21,7 @@ type ProxyConfigEntity struct {
|
|||||||
TenantID int `json:"tenant_id" gorm:"index"`
|
TenantID int `json:"tenant_id" gorm:"index"`
|
||||||
OriginClientID string `json:"origin_client_id" gorm:"index"`
|
OriginClientID string `json:"origin_client_id" gorm:"index"`
|
||||||
Content []byte `json:"content"`
|
Content []byte `json:"content"`
|
||||||
|
Stopped bool `json:"stopped" gorm:"index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*ProxyConfig) TableName() string {
|
func (*ProxyConfig) TableName() string {
|
||||||
@@ -46,3 +47,9 @@ func (p *ProxyConfigEntity) FillClientConfig(cli *ClientEntity) error {
|
|||||||
p.OriginClientID = cli.OriginClientID
|
p.OriginClientID = cli.OriginClientID
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ProxyConfigEntity) GetTypedProxyConfig() (v1.TypedProxyConfig, error) {
|
||||||
|
var cfg v1.TypedProxyConfig
|
||||||
|
err := cfg.UnmarshalJSON(p.Content)
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
@@ -1501,6 +1501,214 @@ func (x *GetProxyConfigResponse) GetWorkingStatus() *ProxyWorkingStatus {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StopProxyRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
ClientId *string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3,oneof" json:"client_id,omitempty"`
|
||||||
|
ServerId *string `protobuf:"bytes,2,opt,name=server_id,json=serverId,proto3,oneof" json:"server_id,omitempty"`
|
||||||
|
Name *string `protobuf:"bytes,3,opt,name=name,proto3,oneof" json:"name,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopProxyRequest) Reset() {
|
||||||
|
*x = StopProxyRequest{}
|
||||||
|
mi := &file_api_client_proto_msgTypes[28]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopProxyRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StopProxyRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StopProxyRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_client_proto_msgTypes[28]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StopProxyRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StopProxyRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_client_proto_rawDescGZIP(), []int{28}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopProxyRequest) GetClientId() string {
|
||||||
|
if x != nil && x.ClientId != nil {
|
||||||
|
return *x.ClientId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopProxyRequest) GetServerId() string {
|
||||||
|
if x != nil && x.ServerId != nil {
|
||||||
|
return *x.ServerId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopProxyRequest) GetName() string {
|
||||||
|
if x != nil && x.Name != nil {
|
||||||
|
return *x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type StopProxyResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Status *Status `protobuf:"bytes,1,opt,name=status,proto3,oneof" json:"status,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopProxyResponse) Reset() {
|
||||||
|
*x = StopProxyResponse{}
|
||||||
|
mi := &file_api_client_proto_msgTypes[29]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopProxyResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StopProxyResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StopProxyResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_client_proto_msgTypes[29]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StopProxyResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StopProxyResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_client_proto_rawDescGZIP(), []int{29}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopProxyResponse) GetStatus() *Status {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartProxyRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
ClientId *string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3,oneof" json:"client_id,omitempty"`
|
||||||
|
ServerId *string `protobuf:"bytes,2,opt,name=server_id,json=serverId,proto3,oneof" json:"server_id,omitempty"`
|
||||||
|
Name *string `protobuf:"bytes,3,opt,name=name,proto3,oneof" json:"name,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartProxyRequest) Reset() {
|
||||||
|
*x = StartProxyRequest{}
|
||||||
|
mi := &file_api_client_proto_msgTypes[30]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartProxyRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StartProxyRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StartProxyRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_client_proto_msgTypes[30]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StartProxyRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StartProxyRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_client_proto_rawDescGZIP(), []int{30}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartProxyRequest) GetClientId() string {
|
||||||
|
if x != nil && x.ClientId != nil {
|
||||||
|
return *x.ClientId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartProxyRequest) GetServerId() string {
|
||||||
|
if x != nil && x.ServerId != nil {
|
||||||
|
return *x.ServerId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartProxyRequest) GetName() string {
|
||||||
|
if x != nil && x.Name != nil {
|
||||||
|
return *x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartProxyResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Status *Status `protobuf:"bytes,1,opt,name=status,proto3,oneof" json:"status,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartProxyResponse) Reset() {
|
||||||
|
*x = StartProxyResponse{}
|
||||||
|
mi := &file_api_client_proto_msgTypes[31]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartProxyResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StartProxyResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StartProxyResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_client_proto_msgTypes[31]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StartProxyResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StartProxyResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_client_proto_rawDescGZIP(), []int{31}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartProxyResponse) GetStatus() *Status {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_api_client_proto protoreflect.FileDescriptor
|
var File_api_client_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_api_client_proto_rawDesc = "" +
|
const file_api_client_proto_rawDesc = "" +
|
||||||
@@ -1678,7 +1886,31 @@ const file_api_client_proto_rawDesc = "" +
|
|||||||
"\x0eworking_status\x18\x03 \x01(\v2\x1a.common.ProxyWorkingStatusH\x02R\rworkingStatus\x88\x01\x01B\t\n" +
|
"\x0eworking_status\x18\x03 \x01(\v2\x1a.common.ProxyWorkingStatusH\x02R\rworkingStatus\x88\x01\x01B\t\n" +
|
||||||
"\a_statusB\x0f\n" +
|
"\a_statusB\x0f\n" +
|
||||||
"\r_proxy_configB\x11\n" +
|
"\r_proxy_configB\x11\n" +
|
||||||
"\x0f_working_statusB\aZ\x05../pbb\x06proto3"
|
"\x0f_working_status\"\x94\x01\n" +
|
||||||
|
"\x10StopProxyRequest\x12 \n" +
|
||||||
|
"\tclient_id\x18\x01 \x01(\tH\x00R\bclientId\x88\x01\x01\x12 \n" +
|
||||||
|
"\tserver_id\x18\x02 \x01(\tH\x01R\bserverId\x88\x01\x01\x12\x17\n" +
|
||||||
|
"\x04name\x18\x03 \x01(\tH\x02R\x04name\x88\x01\x01B\f\n" +
|
||||||
|
"\n" +
|
||||||
|
"_client_idB\f\n" +
|
||||||
|
"\n" +
|
||||||
|
"_server_idB\a\n" +
|
||||||
|
"\x05_name\"K\n" +
|
||||||
|
"\x11StopProxyResponse\x12+\n" +
|
||||||
|
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" +
|
||||||
|
"\a_status\"\x95\x01\n" +
|
||||||
|
"\x11StartProxyRequest\x12 \n" +
|
||||||
|
"\tclient_id\x18\x01 \x01(\tH\x00R\bclientId\x88\x01\x01\x12 \n" +
|
||||||
|
"\tserver_id\x18\x02 \x01(\tH\x01R\bserverId\x88\x01\x01\x12\x17\n" +
|
||||||
|
"\x04name\x18\x03 \x01(\tH\x02R\x04name\x88\x01\x01B\f\n" +
|
||||||
|
"\n" +
|
||||||
|
"_client_idB\f\n" +
|
||||||
|
"\n" +
|
||||||
|
"_server_idB\a\n" +
|
||||||
|
"\x05_name\"L\n" +
|
||||||
|
"\x12StartProxyResponse\x12+\n" +
|
||||||
|
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" +
|
||||||
|
"\a_statusB\aZ\x05../pbb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_api_client_proto_rawDescOnce sync.Once
|
file_api_client_proto_rawDescOnce sync.Once
|
||||||
@@ -1692,7 +1924,7 @@ func file_api_client_proto_rawDescGZIP() []byte {
|
|||||||
return file_api_client_proto_rawDescData
|
return file_api_client_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_api_client_proto_msgTypes = make([]protoimpl.MessageInfo, 28)
|
var file_api_client_proto_msgTypes = make([]protoimpl.MessageInfo, 32)
|
||||||
var file_api_client_proto_goTypes = []any{
|
var file_api_client_proto_goTypes = []any{
|
||||||
(*InitClientRequest)(nil), // 0: api_client.InitClientRequest
|
(*InitClientRequest)(nil), // 0: api_client.InitClientRequest
|
||||||
(*InitClientResponse)(nil), // 1: api_client.InitClientResponse
|
(*InitClientResponse)(nil), // 1: api_client.InitClientResponse
|
||||||
@@ -1722,38 +1954,44 @@ var file_api_client_proto_goTypes = []any{
|
|||||||
(*UpdateProxyConfigResponse)(nil), // 25: api_client.UpdateProxyConfigResponse
|
(*UpdateProxyConfigResponse)(nil), // 25: api_client.UpdateProxyConfigResponse
|
||||||
(*GetProxyConfigRequest)(nil), // 26: api_client.GetProxyConfigRequest
|
(*GetProxyConfigRequest)(nil), // 26: api_client.GetProxyConfigRequest
|
||||||
(*GetProxyConfigResponse)(nil), // 27: api_client.GetProxyConfigResponse
|
(*GetProxyConfigResponse)(nil), // 27: api_client.GetProxyConfigResponse
|
||||||
(*Status)(nil), // 28: common.Status
|
(*StopProxyRequest)(nil), // 28: api_client.StopProxyRequest
|
||||||
(*Client)(nil), // 29: common.Client
|
(*StopProxyResponse)(nil), // 29: api_client.StopProxyResponse
|
||||||
(*ProxyInfo)(nil), // 30: common.ProxyInfo
|
(*StartProxyRequest)(nil), // 30: api_client.StartProxyRequest
|
||||||
(*ProxyConfig)(nil), // 31: common.ProxyConfig
|
(*StartProxyResponse)(nil), // 31: api_client.StartProxyResponse
|
||||||
(*ProxyWorkingStatus)(nil), // 32: common.ProxyWorkingStatus
|
(*Status)(nil), // 32: common.Status
|
||||||
|
(*Client)(nil), // 33: common.Client
|
||||||
|
(*ProxyInfo)(nil), // 34: common.ProxyInfo
|
||||||
|
(*ProxyConfig)(nil), // 35: common.ProxyConfig
|
||||||
|
(*ProxyWorkingStatus)(nil), // 36: common.ProxyWorkingStatus
|
||||||
}
|
}
|
||||||
var file_api_client_proto_depIdxs = []int32{
|
var file_api_client_proto_depIdxs = []int32{
|
||||||
28, // 0: api_client.InitClientResponse.status:type_name -> common.Status
|
32, // 0: api_client.InitClientResponse.status:type_name -> common.Status
|
||||||
28, // 1: api_client.ListClientsResponse.status:type_name -> common.Status
|
32, // 1: api_client.ListClientsResponse.status:type_name -> common.Status
|
||||||
29, // 2: api_client.ListClientsResponse.clients:type_name -> common.Client
|
33, // 2: api_client.ListClientsResponse.clients:type_name -> common.Client
|
||||||
28, // 3: api_client.GetClientResponse.status:type_name -> common.Status
|
32, // 3: api_client.GetClientResponse.status:type_name -> common.Status
|
||||||
29, // 4: api_client.GetClientResponse.client:type_name -> common.Client
|
33, // 4: api_client.GetClientResponse.client:type_name -> common.Client
|
||||||
28, // 5: api_client.DeleteClientResponse.status:type_name -> common.Status
|
32, // 5: api_client.DeleteClientResponse.status:type_name -> common.Status
|
||||||
28, // 6: api_client.UpdateFRPCResponse.status:type_name -> common.Status
|
32, // 6: api_client.UpdateFRPCResponse.status:type_name -> common.Status
|
||||||
28, // 7: api_client.RemoveFRPCResponse.status:type_name -> common.Status
|
32, // 7: api_client.RemoveFRPCResponse.status:type_name -> common.Status
|
||||||
28, // 8: api_client.StopFRPCResponse.status:type_name -> common.Status
|
32, // 8: api_client.StopFRPCResponse.status:type_name -> common.Status
|
||||||
28, // 9: api_client.StartFRPCResponse.status:type_name -> common.Status
|
32, // 9: api_client.StartFRPCResponse.status:type_name -> common.Status
|
||||||
28, // 10: api_client.GetProxyStatsByClientIDResponse.status:type_name -> common.Status
|
32, // 10: api_client.GetProxyStatsByClientIDResponse.status:type_name -> common.Status
|
||||||
30, // 11: api_client.GetProxyStatsByClientIDResponse.proxy_infos:type_name -> common.ProxyInfo
|
34, // 11: api_client.GetProxyStatsByClientIDResponse.proxy_infos:type_name -> common.ProxyInfo
|
||||||
28, // 12: api_client.ListProxyConfigsResponse.status:type_name -> common.Status
|
32, // 12: api_client.ListProxyConfigsResponse.status:type_name -> common.Status
|
||||||
31, // 13: api_client.ListProxyConfigsResponse.proxy_configs:type_name -> common.ProxyConfig
|
35, // 13: api_client.ListProxyConfigsResponse.proxy_configs:type_name -> common.ProxyConfig
|
||||||
28, // 14: api_client.CreateProxyConfigResponse.status:type_name -> common.Status
|
32, // 14: api_client.CreateProxyConfigResponse.status:type_name -> common.Status
|
||||||
28, // 15: api_client.DeleteProxyConfigResponse.status:type_name -> common.Status
|
32, // 15: api_client.DeleteProxyConfigResponse.status:type_name -> common.Status
|
||||||
28, // 16: api_client.UpdateProxyConfigResponse.status:type_name -> common.Status
|
32, // 16: api_client.UpdateProxyConfigResponse.status:type_name -> common.Status
|
||||||
28, // 17: api_client.GetProxyConfigResponse.status:type_name -> common.Status
|
32, // 17: api_client.GetProxyConfigResponse.status:type_name -> common.Status
|
||||||
31, // 18: api_client.GetProxyConfigResponse.proxy_config:type_name -> common.ProxyConfig
|
35, // 18: api_client.GetProxyConfigResponse.proxy_config:type_name -> common.ProxyConfig
|
||||||
32, // 19: api_client.GetProxyConfigResponse.working_status:type_name -> common.ProxyWorkingStatus
|
36, // 19: api_client.GetProxyConfigResponse.working_status:type_name -> common.ProxyWorkingStatus
|
||||||
20, // [20:20] is the sub-list for method output_type
|
32, // 20: api_client.StopProxyResponse.status:type_name -> common.Status
|
||||||
20, // [20:20] is the sub-list for method input_type
|
32, // 21: api_client.StartProxyResponse.status:type_name -> common.Status
|
||||||
20, // [20:20] is the sub-list for extension type_name
|
22, // [22:22] is the sub-list for method output_type
|
||||||
20, // [20:20] is the sub-list for extension extendee
|
22, // [22:22] is the sub-list for method input_type
|
||||||
0, // [0:20] is the sub-list for field type_name
|
22, // [22:22] is the sub-list for extension type_name
|
||||||
|
22, // [22:22] is the sub-list for extension extendee
|
||||||
|
0, // [0:22] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_api_client_proto_init() }
|
func init() { file_api_client_proto_init() }
|
||||||
@@ -1790,13 +2028,17 @@ func file_api_client_proto_init() {
|
|||||||
file_api_client_proto_msgTypes[25].OneofWrappers = []any{}
|
file_api_client_proto_msgTypes[25].OneofWrappers = []any{}
|
||||||
file_api_client_proto_msgTypes[26].OneofWrappers = []any{}
|
file_api_client_proto_msgTypes[26].OneofWrappers = []any{}
|
||||||
file_api_client_proto_msgTypes[27].OneofWrappers = []any{}
|
file_api_client_proto_msgTypes[27].OneofWrappers = []any{}
|
||||||
|
file_api_client_proto_msgTypes[28].OneofWrappers = []any{}
|
||||||
|
file_api_client_proto_msgTypes[29].OneofWrappers = []any{}
|
||||||
|
file_api_client_proto_msgTypes[30].OneofWrappers = []any{}
|
||||||
|
file_api_client_proto_msgTypes[31].OneofWrappers = []any{}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_client_proto_rawDesc), len(file_api_client_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_client_proto_rawDesc), len(file_api_client_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 28,
|
NumMessages: 32,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
|
@@ -248,6 +248,7 @@ type GetPlatformInfoResponse struct {
|
|||||||
MasterApiScheme string `protobuf:"bytes,11,opt,name=master_api_scheme,json=masterApiScheme,proto3" json:"master_api_scheme,omitempty"`
|
MasterApiScheme string `protobuf:"bytes,11,opt,name=master_api_scheme,json=masterApiScheme,proto3" json:"master_api_scheme,omitempty"`
|
||||||
ClientRpcUrl string `protobuf:"bytes,12,opt,name=client_rpc_url,json=clientRpcUrl,proto3" json:"client_rpc_url,omitempty"`
|
ClientRpcUrl string `protobuf:"bytes,12,opt,name=client_rpc_url,json=clientRpcUrl,proto3" json:"client_rpc_url,omitempty"`
|
||||||
ClientApiUrl string `protobuf:"bytes,13,opt,name=client_api_url,json=clientApiUrl,proto3" json:"client_api_url,omitempty"`
|
ClientApiUrl string `protobuf:"bytes,13,opt,name=client_api_url,json=clientApiUrl,proto3" json:"client_api_url,omitempty"`
|
||||||
|
GithubProxyUrl string `protobuf:"bytes,14,opt,name=github_proxy_url,json=githubProxyUrl,proto3" json:"github_proxy_url,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -373,6 +374,13 @@ func (x *GetPlatformInfoResponse) GetClientApiUrl() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *GetPlatformInfoResponse) GetGithubProxyUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.GithubProxyUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
var File_api_user_proto protoreflect.FileDescriptor
|
var File_api_user_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_api_user_proto_rawDesc = "" +
|
const file_api_user_proto_rawDesc = "" +
|
||||||
@@ -392,7 +400,7 @@ const file_api_user_proto_rawDesc = "" +
|
|||||||
"\x16UpdateUserInfoResponse\x12+\n" +
|
"\x16UpdateUserInfoResponse\x12+\n" +
|
||||||
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" +
|
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01B\t\n" +
|
||||||
"\a_status\"\x18\n" +
|
"\a_status\"\x18\n" +
|
||||||
"\x16GetPlatformInfoRequest\"\x85\x05\n" +
|
"\x16GetPlatformInfoRequest\"\xaf\x05\n" +
|
||||||
"\x17GetPlatformInfoResponse\x12+\n" +
|
"\x17GetPlatformInfoResponse\x12+\n" +
|
||||||
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01\x12,\n" +
|
"\x06status\x18\x01 \x01(\v2\x0e.common.StatusH\x00R\x06status\x88\x01\x01\x12,\n" +
|
||||||
"\x12total_client_count\x18\x02 \x01(\x05R\x10totalClientCount\x12,\n" +
|
"\x12total_client_count\x18\x02 \x01(\x05R\x10totalClientCount\x12,\n" +
|
||||||
@@ -407,7 +415,8 @@ const file_api_user_proto_rawDesc = "" +
|
|||||||
" \x01(\x05R\rmasterApiPort\x12*\n" +
|
" \x01(\x05R\rmasterApiPort\x12*\n" +
|
||||||
"\x11master_api_scheme\x18\v \x01(\tR\x0fmasterApiScheme\x12$\n" +
|
"\x11master_api_scheme\x18\v \x01(\tR\x0fmasterApiScheme\x12$\n" +
|
||||||
"\x0eclient_rpc_url\x18\f \x01(\tR\fclientRpcUrl\x12$\n" +
|
"\x0eclient_rpc_url\x18\f \x01(\tR\fclientRpcUrl\x12$\n" +
|
||||||
"\x0eclient_api_url\x18\r \x01(\tR\fclientApiUrlB\t\n" +
|
"\x0eclient_api_url\x18\r \x01(\tR\fclientApiUrl\x12(\n" +
|
||||||
|
"\x10github_proxy_url\x18\x0e \x01(\tR\x0egithubProxyUrlB\t\n" +
|
||||||
"\a_statusB\aZ\x05../pbb\x06proto3"
|
"\a_statusB\aZ\x05../pbb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@@ -704,6 +704,7 @@ type ProxyConfig struct {
|
|||||||
ServerId *string `protobuf:"bytes,5,opt,name=server_id,json=serverId,proto3,oneof" json:"server_id,omitempty"`
|
ServerId *string `protobuf:"bytes,5,opt,name=server_id,json=serverId,proto3,oneof" json:"server_id,omitempty"`
|
||||||
Config *string `protobuf:"bytes,6,opt,name=config,proto3,oneof" json:"config,omitempty"`
|
Config *string `protobuf:"bytes,6,opt,name=config,proto3,oneof" json:"config,omitempty"`
|
||||||
OriginClientId *string `protobuf:"bytes,7,opt,name=origin_client_id,json=originClientId,proto3,oneof" json:"origin_client_id,omitempty"`
|
OriginClientId *string `protobuf:"bytes,7,opt,name=origin_client_id,json=originClientId,proto3,oneof" json:"origin_client_id,omitempty"`
|
||||||
|
Stopped *bool `protobuf:"varint,8,opt,name=stopped,proto3,oneof" json:"stopped,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -787,6 +788,13 @@ func (x *ProxyConfig) GetOriginClientId() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ProxyConfig) GetStopped() bool {
|
||||||
|
if x != nil && x.Stopped != nil {
|
||||||
|
return *x.Stopped
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type ProxyWorkingStatus struct {
|
type ProxyWorkingStatus struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Name *string `protobuf:"bytes,1,opt,name=name,proto3,oneof" json:"name,omitempty"`
|
Name *string `protobuf:"bytes,1,opt,name=name,proto3,oneof" json:"name,omitempty"`
|
||||||
@@ -959,7 +967,7 @@ const file_common_proto_rawDesc = "" +
|
|||||||
"\x12_today_traffic_outB\x15\n" +
|
"\x12_today_traffic_outB\x15\n" +
|
||||||
"\x13_history_traffic_inB\x16\n" +
|
"\x13_history_traffic_inB\x16\n" +
|
||||||
"\x14_history_traffic_outB\r\n" +
|
"\x14_history_traffic_outB\r\n" +
|
||||||
"\v_first_sync\"\xb9\x02\n" +
|
"\v_first_sync\"\xe4\x02\n" +
|
||||||
"\vProxyConfig\x12\x13\n" +
|
"\vProxyConfig\x12\x13\n" +
|
||||||
"\x02id\x18\x01 \x01(\rH\x00R\x02id\x88\x01\x01\x12\x17\n" +
|
"\x02id\x18\x01 \x01(\rH\x00R\x02id\x88\x01\x01\x12\x17\n" +
|
||||||
"\x04name\x18\x02 \x01(\tH\x01R\x04name\x88\x01\x01\x12\x17\n" +
|
"\x04name\x18\x02 \x01(\tH\x01R\x04name\x88\x01\x01\x12\x17\n" +
|
||||||
@@ -967,7 +975,8 @@ const file_common_proto_rawDesc = "" +
|
|||||||
"\tclient_id\x18\x04 \x01(\tH\x03R\bclientId\x88\x01\x01\x12 \n" +
|
"\tclient_id\x18\x04 \x01(\tH\x03R\bclientId\x88\x01\x01\x12 \n" +
|
||||||
"\tserver_id\x18\x05 \x01(\tH\x04R\bserverId\x88\x01\x01\x12\x1b\n" +
|
"\tserver_id\x18\x05 \x01(\tH\x04R\bserverId\x88\x01\x01\x12\x1b\n" +
|
||||||
"\x06config\x18\x06 \x01(\tH\x05R\x06config\x88\x01\x01\x12-\n" +
|
"\x06config\x18\x06 \x01(\tH\x05R\x06config\x88\x01\x01\x12-\n" +
|
||||||
"\x10origin_client_id\x18\a \x01(\tH\x06R\x0eoriginClientId\x88\x01\x01B\x05\n" +
|
"\x10origin_client_id\x18\a \x01(\tH\x06R\x0eoriginClientId\x88\x01\x01\x12\x1d\n" +
|
||||||
|
"\astopped\x18\b \x01(\bH\aR\astopped\x88\x01\x01B\x05\n" +
|
||||||
"\x03_idB\a\n" +
|
"\x03_idB\a\n" +
|
||||||
"\x05_nameB\a\n" +
|
"\x05_nameB\a\n" +
|
||||||
"\x05_typeB\f\n" +
|
"\x05_typeB\f\n" +
|
||||||
@@ -976,7 +985,9 @@ const file_common_proto_rawDesc = "" +
|
|||||||
"\n" +
|
"\n" +
|
||||||
"_server_idB\t\n" +
|
"_server_idB\t\n" +
|
||||||
"\a_configB\x13\n" +
|
"\a_configB\x13\n" +
|
||||||
"\x11_origin_client_id\"\xd5\x01\n" +
|
"\x11_origin_client_idB\n" +
|
||||||
|
"\n" +
|
||||||
|
"\b_stopped\"\xd5\x01\n" +
|
||||||
"\x12ProxyWorkingStatus\x12\x17\n" +
|
"\x12ProxyWorkingStatus\x12\x17\n" +
|
||||||
"\x04name\x18\x01 \x01(\tH\x00R\x04name\x88\x01\x01\x12\x17\n" +
|
"\x04name\x18\x01 \x01(\tH\x00R\x04name\x88\x01\x01\x12\x17\n" +
|
||||||
"\x04type\x18\x02 \x01(\tH\x01R\x04type\x88\x01\x01\x12\x1b\n" +
|
"\x04type\x18\x02 \x01(\tH\x01R\x04type\x88\x01\x01\x12\x1b\n" +
|
||||||
|
@@ -62,6 +62,10 @@ func (c *Context) GetCtx() context.Context {
|
|||||||
return c.Context
|
return c.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) Background() *Context {
|
||||||
|
return NewContext(context.Background(), c.appInstance)
|
||||||
|
}
|
||||||
|
|
||||||
func NewContext(c context.Context, appInstance Application) *Context {
|
func NewContext(c context.Context, appInstance Application) *Context {
|
||||||
return &Context{
|
return &Context{
|
||||||
Context: c,
|
Context: c,
|
||||||
|
@@ -236,6 +236,7 @@ func (q *queryImpl) AdminCreateProxyConfig(proxyCfg *models.ProxyConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RebuildProxyConfigFromClient rebuild proxy from client
|
// RebuildProxyConfigFromClient rebuild proxy from client
|
||||||
|
// skip stopped proxy
|
||||||
func (q *queryImpl) RebuildProxyConfigFromClient(userInfo models.UserInfo, client *models.Client) error {
|
func (q *queryImpl) RebuildProxyConfigFromClient(userInfo models.UserInfo, client *models.Client) error {
|
||||||
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
|
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
|
||||||
|
|
||||||
@@ -460,16 +461,19 @@ func (q *queryImpl) DeleteProxyConfigsByClientIDOrOriginClientID(userInfo models
|
|||||||
}
|
}
|
||||||
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
|
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
|
||||||
return db.Unscoped().
|
return db.Unscoped().
|
||||||
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
|
Where(
|
||||||
UserID: userInfo.GetUserID(),
|
db.Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
|
||||||
TenantID: userInfo.GetTenantID(),
|
UserID: userInfo.GetUserID(),
|
||||||
ClientID: clientID,
|
TenantID: userInfo.GetTenantID(),
|
||||||
}}).
|
ClientID: clientID,
|
||||||
Or(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
|
}}).
|
||||||
UserID: userInfo.GetUserID(),
|
Or(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
|
||||||
TenantID: userInfo.GetTenantID(),
|
UserID: userInfo.GetUserID(),
|
||||||
OriginClientID: clientID,
|
TenantID: userInfo.GetTenantID(),
|
||||||
}}).
|
OriginClientID: clientID,
|
||||||
|
}})).
|
||||||
|
Where(db.Where("stopped is NULL").
|
||||||
|
Or("stopped = ?", false)).
|
||||||
Delete(&models.ProxyConfig{}).Error
|
Delete(&models.ProxyConfig{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,8 +9,12 @@ import {
|
|||||||
GetProxyConfigResponse,
|
GetProxyConfigResponse,
|
||||||
ListProxyConfigsRequest,
|
ListProxyConfigsRequest,
|
||||||
ListProxyConfigsResponse,
|
ListProxyConfigsResponse,
|
||||||
|
StartProxyRequest,
|
||||||
|
StartProxyResponse,
|
||||||
|
StopProxyRequest,
|
||||||
|
StopProxyResponse,
|
||||||
UpdateProxyConfigRequest,
|
UpdateProxyConfigRequest,
|
||||||
UpdateProxyConfigResponse
|
UpdateProxyConfigResponse,
|
||||||
} from '@/lib/pb/api_client'
|
} from '@/lib/pb/api_client'
|
||||||
import { BaseResponse } from '@/types/api'
|
import { BaseResponse } from '@/types/api'
|
||||||
|
|
||||||
@@ -37,4 +41,14 @@ export const deleteProxyConfig = async (req: DeleteProxyConfigRequest) => {
|
|||||||
export const getProxyConfig = async (req: GetProxyConfigRequest) => {
|
export const getProxyConfig = async (req: GetProxyConfigRequest) => {
|
||||||
const res = await http.post(API_PATH + '/proxy/get_config', GetProxyConfigRequest.toJson(req))
|
const res = await http.post(API_PATH + '/proxy/get_config', GetProxyConfigRequest.toJson(req))
|
||||||
return GetProxyConfigResponse.fromJson((res.data as BaseResponse).body)
|
return GetProxyConfigResponse.fromJson((res.data as BaseResponse).body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const stopProxy = async (req: StopProxyRequest) => {
|
||||||
|
const res = await http.post(API_PATH + '/proxy/stop_proxy', StopProxyRequest.toJson(req))
|
||||||
|
return StopProxyResponse.fromJson((res.data as BaseResponse).body)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startProxy = async (req: StartProxyRequest) => {
|
||||||
|
const res = await http.post(API_PATH + '/proxy/start_proxy', StartProxyRequest.toJson(req))
|
||||||
|
return StartProxyResponse.fromJson((res.data as BaseResponse).body)
|
||||||
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Button } from "@/components/ui/button"
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -7,26 +7,25 @@ import {
|
|||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from '@/components/ui/dropdown-menu'
|
||||||
import { LucideIcon } from "lucide-react"
|
import { LucideIcon } from 'lucide-react'
|
||||||
|
|
||||||
export interface DropdownMenuProps {
|
export interface DropdownMenuProps {
|
||||||
menuGroup: {
|
menuGroup: {
|
||||||
name: string,
|
name: string
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
icon?: LucideIcon
|
icon?: LucideIcon
|
||||||
className?: string
|
className?: string
|
||||||
}[][]
|
}[][]
|
||||||
title: string
|
title: string
|
||||||
trigger: React.ReactNode
|
trigger: React.ReactNode
|
||||||
|
extraButtons?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BaseDropdownMenu({ menuGroup, title, trigger }: DropdownMenuProps) {
|
export function BaseDropdownMenu({ menuGroup, title, trigger, extraButtons }: DropdownMenuProps) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>{trigger || <Button variant="outline">Open</Button>}</DropdownMenuTrigger>
|
||||||
{trigger || <Button variant="outline">Open</Button>}
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="w-fit">
|
<DropdownMenuContent className="w-fit">
|
||||||
<DropdownMenuLabel>{title}</DropdownMenuLabel>
|
<DropdownMenuLabel>{title}</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
@@ -40,6 +39,7 @@ export function BaseDropdownMenu({ menuGroup, title, trigger }: DropdownMenuProp
|
|||||||
))}
|
))}
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
))}
|
))}
|
||||||
|
{extraButtons && <DropdownMenuGroup>{extraButtons}</DropdownMenuGroup>}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)
|
)
|
||||||
|
@@ -26,7 +26,7 @@ import { useMutation, useQuery } from '@tanstack/react-query'
|
|||||||
import { deleteClient, listClient } from '@/api/client'
|
import { deleteClient, listClient } from '@/api/client'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { $platformInfo } from '@/store/user'
|
import { $platformInfo, $useServerGithubProxyUrl } from '@/store/user'
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||||
import { getClientsStatus } from '@/api/platform'
|
import { getClientsStatus } from '@/api/platform'
|
||||||
import { Client, ClientType } from '@/lib/pb/common'
|
import { Client, ClientType } from '@/lib/pb/common'
|
||||||
@@ -39,6 +39,8 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { $clientTableRefetchTrigger } from '@/store/refetch-trigger'
|
import { $clientTableRefetchTrigger } from '@/store/refetch-trigger'
|
||||||
import { NeedUpgrade } from '@/config/notify'
|
import { NeedUpgrade } from '@/config/notify'
|
||||||
|
import { Label } from '../ui/label'
|
||||||
|
import { Checkbox } from '../ui/checkbox'
|
||||||
|
|
||||||
export type ClientTableSchema = {
|
export type ClientTableSchema = {
|
||||||
id: string
|
id: string
|
||||||
@@ -110,7 +112,12 @@ export const columns: ColumnDef<ClientTableSchema>[] = [
|
|||||||
id: 'action',
|
id: 'action',
|
||||||
cell: ({ row, table }) => {
|
cell: ({ row, table }) => {
|
||||||
const client = row.original
|
const client = row.original
|
||||||
return <ClientActions client={client} table={table as Table<ClientTableSchema> & { options: { meta: TableMetaType } }} />
|
return (
|
||||||
|
<ClientActions
|
||||||
|
client={client}
|
||||||
|
table={table as Table<ClientTableSchema> & { options: { meta: TableMetaType } }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -118,6 +125,7 @@ export const columns: ColumnDef<ClientTableSchema>[] = [
|
|||||||
export const ClientID = ({ client }: { client: ClientTableSchema }) => {
|
export const ClientID = ({ client }: { client: ClientTableSchema }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const platformInfo = useStore($platformInfo)
|
const platformInfo = useStore($platformInfo)
|
||||||
|
const useGithubProxyUrl = useStore($useServerGithubProxyUrl)
|
||||||
|
|
||||||
if (!platformInfo) {
|
if (!platformInfo) {
|
||||||
return (
|
return (
|
||||||
@@ -141,14 +149,22 @@ export const ClientID = ({ client }: { client: ClientTableSchema }) => {
|
|||||||
<p className="text-sm text-muted-foreground">{t('client.install.description')}</p>
|
<p className="text-sm text-muted-foreground">{t('client.install.description')}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
|
<div className="flex flex-row justify-start items-center gap-4 mb-2">
|
||||||
|
<Checkbox onCheckedChange={$useServerGithubProxyUrl.set} defaultChecked={useGithubProxyUrl} />
|
||||||
|
<Label>{t('client.install.use_github_proxy_url')}</Label>
|
||||||
|
</div>
|
||||||
<div className="grid grid-cols-2 items-center gap-4">
|
<div className="grid grid-cols-2 items-center gap-4">
|
||||||
<Input
|
<Input
|
||||||
readOnly
|
readOnly
|
||||||
value={WindowsInstallCommand('client', client, platformInfo)}
|
value={WindowsInstallCommand('client', client, platformInfo, useGithubProxyUrl)}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigator.clipboard.writeText(WindowsInstallCommand('client', client, platformInfo))}
|
onClick={() =>
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
WindowsInstallCommand('client', client, platformInfo, useGithubProxyUrl),
|
||||||
|
)
|
||||||
|
}
|
||||||
disabled={!platformInfo}
|
disabled={!platformInfo}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -159,11 +175,13 @@ export const ClientID = ({ client }: { client: ClientTableSchema }) => {
|
|||||||
<div className="grid grid-cols-2 items-center gap-4">
|
<div className="grid grid-cols-2 items-center gap-4">
|
||||||
<Input
|
<Input
|
||||||
readOnly
|
readOnly
|
||||||
value={LinuxInstallCommand('client', client, platformInfo)}
|
value={LinuxInstallCommand('client', client, platformInfo, useGithubProxyUrl)}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigator.clipboard.writeText(LinuxInstallCommand('client', client, platformInfo))}
|
onClick={() =>
|
||||||
|
navigator.clipboard.writeText(LinuxInstallCommand('client', client, platformInfo, useGithubProxyUrl))
|
||||||
|
}
|
||||||
disabled={!platformInfo}
|
disabled={!platformInfo}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -191,9 +209,12 @@ export const ClientInfo = ({ client }: { client: ClientTableSchema }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const trans = (info: ClientStatus | undefined) => {
|
const trans = (info: ClientStatus | undefined) => {
|
||||||
let statusText: 'client.status_online' | 'client.status_offline' |
|
let statusText:
|
||||||
'client.status_error' | 'client.status_pause' |
|
| 'client.status_online'
|
||||||
'client.status_unknown' = 'client.status_unknown'
|
| 'client.status_offline'
|
||||||
|
| 'client.status_error'
|
||||||
|
| 'client.status_pause'
|
||||||
|
| 'client.status_unknown' = 'client.status_unknown'
|
||||||
if (info === undefined) {
|
if (info === undefined) {
|
||||||
return statusText
|
return statusText
|
||||||
}
|
}
|
||||||
@@ -211,26 +232,28 @@ export const ClientInfo = ({ client }: { client: ClientTableSchema }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const infoColor =
|
const infoColor =
|
||||||
clientsStatus?.clients[client.id]?.status === ClientStatus_Status.ONLINE ? (
|
clientsStatus?.clients[client.id]?.status === ClientStatus_Status.ONLINE
|
||||||
client.stopped ? 'text-yellow-500' : 'text-green-500') : 'text-red-500'
|
? client.stopped
|
||||||
|
? 'text-yellow-500'
|
||||||
|
: 'text-green-500'
|
||||||
|
: 'text-red-500'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 flex-row">
|
<div className="flex items-center gap-2 flex-row">
|
||||||
<Badge variant={"secondary"} className={`p-2 border font-mono w-fit ${infoColor} text-nowrap rounded-full h-6`}>
|
<Badge variant={'secondary'} className={`p-2 border font-mono w-fit ${infoColor} text-nowrap rounded-full h-6`}>
|
||||||
{`${clientsStatus?.clients[client.id].ping}ms,${t(trans(clientsStatus?.clients[client.id]))}`}
|
{`${clientsStatus?.clients[client.id].ping}ms,${t(trans(clientsStatus?.clients[client.id]))}`}
|
||||||
</Badge>
|
</Badge>
|
||||||
{clientsStatus?.clients[client.id].version &&
|
{clientsStatus?.clients[client.id].version && <ClientDetail clientStatus={clientsStatus?.clients[client.id]} />}
|
||||||
<ClientDetail clientStatus={clientsStatus?.clients[client.id]} />
|
{NeedUpgrade(clientsStatus?.clients[client.id].version) && (
|
||||||
}
|
<Badge variant={'destructive'} className={`p-2 border font-mono w-fit text-nowrap rounded-full h-6`}>
|
||||||
{NeedUpgrade(clientsStatus?.clients[client.id].version) &&
|
{t('client.need_upgrade')}
|
||||||
<Badge variant={"destructive"} className={`p-2 border font-mono w-fit text-nowrap rounded-full h-6`}>
|
|
||||||
{`需要升级!`}
|
|
||||||
</Badge>
|
</Badge>
|
||||||
}
|
)}
|
||||||
{client.originClient.ephemeral && <Badge variant={"secondary"} className={`p-2 border font-mono w-fit text-nowrap rounded-full h-6`}>
|
{client.originClient.ephemeral && (
|
||||||
{`临时`}
|
<Badge variant={'secondary'} className={`p-2 border font-mono w-fit text-nowrap rounded-full h-6`}>
|
||||||
</Badge>}
|
{t('client.temp_node')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -251,9 +274,7 @@ export const ClientSecret = ({ client }: { client: ClientTableSchema }) => {
|
|||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<div className="group relative cursor-pointer inline-block font-mono text-nowrap">
|
<div className="group relative cursor-pointer inline-block font-mono text-nowrap">
|
||||||
<span className="opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
<span className="opacity-0 group-hover:opacity-100 transition-opacity duration-200">{client.secret}</span>
|
||||||
{client.secret}
|
|
||||||
</span>
|
|
||||||
<span className="absolute inset-0 opacity-100 group-hover:opacity-0 transition-opacity duration-200">
|
<span className="absolute inset-0 opacity-100 group-hover:opacity-0 transition-opacity duration-200">
|
||||||
{'*'.repeat(client.secret.length)}
|
{'*'.repeat(client.secret.length)}
|
||||||
</span>
|
</span>
|
||||||
@@ -264,7 +285,16 @@ export const ClientSecret = ({ client }: { client: ClientTableSchema }) => {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="font-medium leading-none">{t('client.start.title')}</h4>
|
<h4 className="font-medium leading-none">{t('client.start.title')}</h4>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{t('client.start.description')} (<a className='text-blue-500' href='https://github.com/VaalaCat/frp-panel/releases' target="_blank" rel="noopener noreferrer">{t('common.download')}</a>)
|
{t('client.start.description')} (
|
||||||
|
<a
|
||||||
|
className="text-blue-500"
|
||||||
|
href="https://github.com/VaalaCat/frp-panel/releases"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{t('common.download')}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
@@ -296,6 +326,7 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const platformInfo = useStore($platformInfo)
|
const platformInfo = useStore($platformInfo)
|
||||||
|
const useGithubProxyUrl = useStore($useServerGithubProxyUrl)
|
||||||
|
|
||||||
const removeClient = useMutation({
|
const removeClient = useMutation({
|
||||||
mutationFn: deleteClient,
|
mutationFn: deleteClient,
|
||||||
@@ -370,12 +401,30 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast(t('client.actions_menu.copy_failed'), {
|
toast(t('client.actions_menu.copy_failed'), {
|
||||||
description: JSON.stringify(error)
|
description: JSON.stringify(error),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('client.actions_menu.copy_command')}
|
{t('client.actions_menu.copy_start_command')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
try {
|
||||||
|
if (platformInfo) {
|
||||||
|
navigator.clipboard.writeText(LinuxInstallCommand('client', client, platformInfo, useGithubProxyUrl))
|
||||||
|
toast(t('client.actions_menu.copy_success'))
|
||||||
|
} else {
|
||||||
|
toast(t('client.actions_menu.copy_failed'))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast(t('client.actions_menu.copy_failed'), {
|
||||||
|
description: JSON.stringify(error),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('client.actions_menu.copy_install_command')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -393,7 +442,7 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast(t('client.actions_menu.download_failed'), {
|
toast(t('client.actions_menu.download_failed'), {
|
||||||
description: JSON.stringify(error)
|
description: JSON.stringify(error),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -402,14 +451,20 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push({ pathname: '/streamlog', query: { clientID: client.id, clientType: ClientType.FRPC.toString() } })
|
router.push({
|
||||||
|
pathname: '/streamlog',
|
||||||
|
query: { clientID: client.id, clientType: ClientType.FRPC.toString() },
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('client.actions_menu.realtime_log')}
|
{t('client.actions_menu.realtime_log')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push({ pathname: '/console', query: { clientID: client.id, clientType: ClientType.FRPC.toString() } })
|
router.push({
|
||||||
|
pathname: '/console',
|
||||||
|
query: { clientID: client.id, clientType: ClientType.FRPC.toString() },
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('client.actions_menu.remote_terminal')}
|
{t('client.actions_menu.remote_terminal')}
|
||||||
@@ -434,9 +489,7 @@ export const ClientActions: React.FC<ClientItemProps> = ({ client, table }) => {
|
|||||||
<DialogTitle>{t('client.delete.title')}</DialogTitle>
|
<DialogTitle>{t('client.delete.title')}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
<p className="text-destructive">{t('client.delete.description')}</p>
|
<p className="text-destructive">{t('client.delete.description')}</p>
|
||||||
<p className="text-gray-500 border-l-4 border-gray-500 pl-4 py-2">
|
<p className="text-gray-500 border-l-4 border-gray-500 pl-4 py-2">{t('client.delete.warning')}</p>
|
||||||
{t('client.delete.warning')}
|
|
||||||
</p>
|
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
115
www/components/frpc/client_plugins.tsx
Normal file
115
www/components/frpc/client_plugins.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect, useMemo } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
TypedClientPluginOptions,
|
||||||
|
ClientPluginType,
|
||||||
|
HTTPProxyPluginOptions,
|
||||||
|
HTTP2HTTPSPluginOptions,
|
||||||
|
HTTPS2HTTPPluginOptions,
|
||||||
|
HTTPS2HTTPSPluginOptions,
|
||||||
|
Socks5PluginOptions,
|
||||||
|
StaticFilePluginOptions,
|
||||||
|
UnixDomainSocketPluginOptions,
|
||||||
|
} from '@/types/plugin'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
|
import { HTTPProxyPluginForm } from './plugins/http_proxy_plugin_form'
|
||||||
|
import { HTTP2HTTPSPluginForm } from './plugins/http_2_https_plugin_form'
|
||||||
|
import { HTTPS2HTTPPluginForm } from './plugins/https_2_http_plugin_form'
|
||||||
|
import { HTTPS2HTTPSPluginForm } from './plugins/https_2_https_plugin_form'
|
||||||
|
import { Socks5PluginForm } from './plugins/socks5_plugin_form'
|
||||||
|
import { StaticFilePluginForm } from './plugins/static_file_plugin_form'
|
||||||
|
import { UnixDomainSocketPluginForm } from './plugins/unix_domain_socket_plugin_form'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
const pluginTypeMap: Record<string, string> = {
|
||||||
|
http_proxy: 'HTTP Proxy',
|
||||||
|
http2https: 'HTTP→HTTPS',
|
||||||
|
https2http: 'HTTPS→HTTP',
|
||||||
|
https2https: 'HTTPS→HTTPS',
|
||||||
|
socks5: 'SOCKS5',
|
||||||
|
static_file: 'Static File',
|
||||||
|
unix_domain_socket: 'Unix Domain Socket',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
defaultPluginConfig?: TypedClientPluginOptions
|
||||||
|
setPluginConfig: (c: TypedClientPluginOptions) => void
|
||||||
|
supportedPlugins?: ClientPluginType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PluginConfigForm({ defaultPluginConfig, setPluginConfig, supportedPlugins }: Props) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [config, updateConfig] = useState(defaultPluginConfig)
|
||||||
|
|
||||||
|
// sync out
|
||||||
|
useEffect(() => {
|
||||||
|
config && setPluginConfig(config)
|
||||||
|
}, [config, setPluginConfig])
|
||||||
|
|
||||||
|
const handleTypeChange = (type: ClientPluginType) => {
|
||||||
|
updateConfig({ ...config, type })
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginsToSelect = useMemo(() => {
|
||||||
|
const plugins = supportedPlugins || Object.keys(pluginTypeMap)
|
||||||
|
return (
|
||||||
|
plugins &&
|
||||||
|
plugins.map((plugin, i) => (
|
||||||
|
<SelectItem key={`${i}`} value={plugin}>
|
||||||
|
{pluginTypeMap[plugin]}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}, [supportedPlugins])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6 p-6 bg-white rounded-lg shadow">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="plugin-type">{t('frpc.client_plugins.plugin_type')}</Label>
|
||||||
|
<Select
|
||||||
|
defaultValue={defaultPluginConfig?.type}
|
||||||
|
onValueChange={(value) => handleTypeChange(value as ClientPluginType)}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="plugin-type">
|
||||||
|
<SelectValue placeholder={t('frpc.client_plugins.select_plugin_type')} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>{pluginsToSelect}</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{config && config.type && config.type.length > 0 ? (
|
||||||
|
<>
|
||||||
|
{config.type === 'http_proxy' && (
|
||||||
|
<HTTPProxyPluginForm config={config as HTTPProxyPluginOptions} setConfig={(c) => updateConfig(c)} />
|
||||||
|
)}
|
||||||
|
{config.type === 'http2https' && (
|
||||||
|
<HTTP2HTTPSPluginForm config={config as HTTP2HTTPSPluginOptions} setConfig={(c) => updateConfig(c)} />
|
||||||
|
)}
|
||||||
|
{config.type === 'https2http' && (
|
||||||
|
<HTTPS2HTTPPluginForm config={config as HTTPS2HTTPPluginOptions} setConfig={(c) => updateConfig(c)} />
|
||||||
|
)}
|
||||||
|
{config.type === 'https2https' && (
|
||||||
|
<HTTPS2HTTPSPluginForm config={config as HTTPS2HTTPSPluginOptions} setConfig={(c) => updateConfig(c)} />
|
||||||
|
)}
|
||||||
|
{config.type === 'socks5' && (
|
||||||
|
<Socks5PluginForm config={config as Socks5PluginOptions} setConfig={(c) => updateConfig(c)} />
|
||||||
|
)}
|
||||||
|
{config.type === 'static_file' && (
|
||||||
|
<StaticFilePluginForm config={config as StaticFilePluginOptions} setConfig={(c) => updateConfig(c)} />
|
||||||
|
)}
|
||||||
|
{config.type === 'unix_domain_socket' && (
|
||||||
|
<UnixDomainSocketPluginForm
|
||||||
|
config={config as UnixDomainSocketPluginOptions}
|
||||||
|
setConfig={(c) => updateConfig(c)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PluginConfigForm
|
39
www/components/frpc/plugins/http_2_https_plugin_form.tsx
Normal file
39
www/components/frpc/plugins/http_2_https_plugin_form.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { HTTP2HTTPSPluginOptions } from '@/types/plugin'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
config: HTTP2HTTPSPluginOptions
|
||||||
|
setConfig: (c: HTTP2HTTPSPluginOptions) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HTTP2HTTPSPluginForm({ config, setConfig }: Props) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="localAddr">{t('frpc.client_plugins.http_local_addr')}</Label>
|
||||||
|
<Input
|
||||||
|
id="localAddr"
|
||||||
|
value={config.localAddr ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, localAddr: e.target.value })}
|
||||||
|
placeholder="127.0.0.1:8080"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="hostHeaderRewrite">{t('frpc.client_plugins.http_host_header_rewrite')}</Label>
|
||||||
|
<Input
|
||||||
|
id="hostHeaderRewrite"
|
||||||
|
value={config.hostHeaderRewrite ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, hostHeaderRewrite: e.target.value })}
|
||||||
|
placeholder="example.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* You could add a custom HeaderOperations component here */}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
39
www/components/frpc/plugins/http_proxy_plugin_form.tsx
Normal file
39
www/components/frpc/plugins/http_proxy_plugin_form.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { HTTPProxyPluginOptions } from '@/types/plugin'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
config: HTTPProxyPluginOptions
|
||||||
|
setConfig: (c: HTTPProxyPluginOptions) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HTTPProxyPluginForm({ config, setConfig }: Props) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="httpUser">{t('frpc.client_plugins.http_user')}</Label>
|
||||||
|
<Input
|
||||||
|
id="httpUser"
|
||||||
|
value={config.httpUser ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, httpUser: e.target.value })}
|
||||||
|
placeholder="username"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="httpPassword">{t('frpc.client_plugins.http_password')}</Label>
|
||||||
|
<Input
|
||||||
|
id="httpPassword"
|
||||||
|
type="password"
|
||||||
|
value={config.httpPassword ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, httpPassword: e.target.value })}
|
||||||
|
placeholder="password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
56
www/components/frpc/plugins/https_2_http_plugin_form.tsx
Normal file
56
www/components/frpc/plugins/https_2_http_plugin_form.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { HTTPS2HTTPPluginOptions } from '@/types/plugin'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
config: HTTPS2HTTPPluginOptions
|
||||||
|
setConfig: (c: HTTPS2HTTPPluginOptions) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HTTPS2HTTPPluginForm({ config, setConfig }: Props) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="localAddr">{t('frpc.plugins.local_addr')}</Label>
|
||||||
|
<Input
|
||||||
|
id="localAddr"
|
||||||
|
value={config.localAddr ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, localAddr: e.target.value })}
|
||||||
|
placeholder="127.0.0.1:8080"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="hostHeaderRewrite">{t('frpc.plugins.host_header_rewrite')}</Label>
|
||||||
|
<Input
|
||||||
|
id="hostHeaderRewrite"
|
||||||
|
value={config.hostHeaderRewrite ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, hostHeaderRewrite: e.target.value })}
|
||||||
|
placeholder="example.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="crtPath">{t('frpc.plugins.crt_path')}</Label>
|
||||||
|
<Input
|
||||||
|
id="crtPath"
|
||||||
|
value={config.crtPath ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, crtPath: e.target.value })}
|
||||||
|
placeholder="/path/to/cert.pem"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="keyPath">{t('frpc.plugins.key_path')}</Label>
|
||||||
|
<Input
|
||||||
|
id="keyPath"
|
||||||
|
value={config.keyPath ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, keyPath: e.target.value })}
|
||||||
|
placeholder="/path/to/key.pem"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
32
www/components/frpc/plugins/https_2_https_plugin_form.tsx
Normal file
32
www/components/frpc/plugins/https_2_https_plugin_form.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { HTTPS2HTTPPluginOptions, HTTPS2HTTPSPluginOptions } from '@/types/plugin'
|
||||||
|
import { HTTPS2HTTPPluginForm } from './https_2_http_plugin_form'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
config: HTTPS2HTTPSPluginOptions
|
||||||
|
setConfig: (c: HTTPS2HTTPSPluginOptions) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HTTPS2HTTPSPluginForm({ config, setConfig }: Props) {
|
||||||
|
const { type, ...rest } = config
|
||||||
|
const setTyped = (c: HTTPS2HTTPPluginOptions) => {
|
||||||
|
setConfig({
|
||||||
|
...rest,
|
||||||
|
...c,
|
||||||
|
type: 'https2https',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* same as HTTPS2HTTP + additional fields */}
|
||||||
|
<HTTPS2HTTPPluginForm
|
||||||
|
config={{
|
||||||
|
type: 'https2http',
|
||||||
|
...rest,
|
||||||
|
}}
|
||||||
|
setConfig={setTyped}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
38
www/components/frpc/plugins/socks5_plugin_form.tsx
Normal file
38
www/components/frpc/plugins/socks5_plugin_form.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { Socks5PluginOptions } from '@/types/plugin'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
config: Socks5PluginOptions
|
||||||
|
setConfig: (c: Socks5PluginOptions) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Socks5PluginForm({ config, setConfig }: Props) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="username">{t('frpc.plugins.username')}</Label>
|
||||||
|
<Input
|
||||||
|
id="username"
|
||||||
|
value={config.username ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, username: e.target.value })}
|
||||||
|
placeholder="username"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="password">{t('frpc.plugins.password')}</Label>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
value={config.password ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, password: e.target.value })}
|
||||||
|
placeholder="password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
55
www/components/frpc/plugins/static_file_plugin_form.tsx
Normal file
55
www/components/frpc/plugins/static_file_plugin_form.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { StaticFilePluginOptions } from '@/types/plugin'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
config: StaticFilePluginOptions
|
||||||
|
setConfig: (c: StaticFilePluginOptions) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StaticFilePluginForm({ config, setConfig }: Props) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="localPath">{t('frpc.plugins.local_path')}</Label>
|
||||||
|
<Input
|
||||||
|
id="localPath"
|
||||||
|
value={config.localPath ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, localPath: e.target.value })}
|
||||||
|
placeholder="/var/www"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="stripPrefix">{t('frpc.plugins.strip_prefix')}</Label>
|
||||||
|
<Input
|
||||||
|
id="stripPrefix"
|
||||||
|
value={config.stripPrefix ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, stripPrefix: e.target.value })}
|
||||||
|
placeholder="/static"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="httpUser">{t('frpc.plugins.http_user')}</Label>
|
||||||
|
<Input
|
||||||
|
id="httpUser"
|
||||||
|
value={config.httpUser ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, httpUser: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="httpPassword">{t('frpc.plugins.http_password')}</Label>
|
||||||
|
<Input
|
||||||
|
id="httpPassword"
|
||||||
|
type="password"
|
||||||
|
value={config.httpPassword ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, httpPassword: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@@ -0,0 +1,27 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { UnixDomainSocketPluginOptions } from '@/types/plugin'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
config: UnixDomainSocketPluginOptions
|
||||||
|
setConfig: (c: UnixDomainSocketPluginOptions) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UnixDomainSocketPluginForm({ config, setConfig }: Props) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="unixPath">{t('frpc.plugins.unix_domain_socket_path')}</Label>
|
||||||
|
<Input
|
||||||
|
id="unixPath"
|
||||||
|
value={config.unixPath ?? ''}
|
||||||
|
onChange={(e) => setConfig({ ...config, unixPath: e.target.value })}
|
||||||
|
placeholder="/tmp/frp.sock"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
import { HTTPProxyConfig, TCPProxyConfig, TypedProxyConfig, UDPProxyConfig, STCPProxyConfig } from '@/types/proxy'
|
import { HTTPProxyConfig, TCPProxyConfig, TypedProxyConfig, UDPProxyConfig, STCPProxyConfig } from '@/types/proxy'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ZodPortSchema, ZodStringOptionalSchema, ZodStringSchema } from '@/lib/consts'
|
import { TypedProxyConfigValid, ZodPortSchema, ZodStringOptionalSchema, ZodStringSchema } from '@/lib/consts'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
@@ -12,31 +12,28 @@ import { YesIcon } from '@/components/ui/icon'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { getServer } from '@/api/server'
|
import { getServer } from '@/api/server'
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from '@/components/ui/switch'
|
||||||
import { VisitPreview } from '../base/visit-preview'
|
import { VisitPreview } from '../base/visit-preview'
|
||||||
import {
|
import { HostField, PortField, SecretStringField, StringArrayField, StringField } from '../base/form-field'
|
||||||
HostField,
|
import PluginConfigForm from './client_plugins'
|
||||||
PortField,
|
import { TypedClientPluginOptions } from '@/types/plugin'
|
||||||
SecretStringField,
|
import { toast } from 'sonner'
|
||||||
StringArrayField,
|
|
||||||
StringField
|
|
||||||
} from '../base/form-field'
|
|
||||||
|
|
||||||
export const TCPConfigSchema = z.object({
|
export const TCPConfigSchema = z.object({
|
||||||
remotePort: ZodPortSchema,
|
remotePort: ZodPortSchema.optional(),
|
||||||
localIP: ZodStringSchema.default('127.0.0.1'),
|
localIP: ZodStringSchema.default('127.0.0.1').optional(),
|
||||||
localPort: ZodPortSchema,
|
localPort: ZodPortSchema.optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const UDPConfigSchema = z.object({
|
export const UDPConfigSchema = z.object({
|
||||||
remotePort: ZodPortSchema.optional(),
|
remotePort: ZodPortSchema.optional(),
|
||||||
localIP: ZodStringSchema.default('127.0.0.1'),
|
localIP: ZodStringSchema.default('127.0.0.1').optional(),
|
||||||
localPort: ZodPortSchema,
|
localPort: ZodPortSchema.optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const HTTPConfigSchema = z.object({
|
export const HTTPConfigSchema = z.object({
|
||||||
localPort: ZodPortSchema,
|
localPort: ZodPortSchema.optional(),
|
||||||
localIP: ZodStringSchema.default('127.0.0.1'),
|
localIP: ZodStringSchema.default('127.0.0.1').optional(),
|
||||||
subdomain: ZodStringOptionalSchema,
|
subdomain: ZodStringOptionalSchema,
|
||||||
locations: z.array(ZodStringSchema).optional(),
|
locations: z.array(ZodStringSchema).optional(),
|
||||||
customDomains: z.array(ZodStringSchema).optional(),
|
customDomains: z.array(ZodStringSchema).optional(),
|
||||||
@@ -45,9 +42,9 @@ export const HTTPConfigSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const STCPConfigSchema = z.object({
|
export const STCPConfigSchema = z.object({
|
||||||
localIP: ZodStringSchema.default('127.0.0.1'),
|
localIP: ZodStringSchema.default('127.0.0.1').optional(),
|
||||||
localPort: ZodPortSchema,
|
localPort: ZodPortSchema.optional(),
|
||||||
secretKey: ZodStringSchema,
|
secretKey: ZodStringSchema.optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export interface ProxyFormProps {
|
export interface ProxyFormProps {
|
||||||
@@ -60,61 +57,79 @@ export interface ProxyFormProps {
|
|||||||
setClientProxyConfigs: React.Dispatch<React.SetStateAction<TypedProxyConfig[]>>
|
setClientProxyConfigs: React.Dispatch<React.SetStateAction<TypedProxyConfig[]>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TypedProxyForm: React.FC<ProxyFormProps> = ({
|
||||||
export const TypedProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs, enablePreview }) => {
|
serverID,
|
||||||
|
clientID,
|
||||||
|
defaultProxyConfig,
|
||||||
|
proxyName,
|
||||||
|
clientProxyConfigs,
|
||||||
|
setClientProxyConfigs,
|
||||||
|
enablePreview,
|
||||||
|
}) => {
|
||||||
if (!defaultProxyConfig) {
|
if (!defaultProxyConfig) {
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<> {defaultProxyConfig.type === 'tcp' && serverID && clientID && (
|
return (
|
||||||
<TCPProxyForm
|
<>
|
||||||
defaultProxyConfig={defaultProxyConfig}
|
{defaultProxyConfig.type === 'tcp' && serverID && clientID && (
|
||||||
proxyName={proxyName}
|
<TCPProxyForm
|
||||||
serverID={serverID}
|
defaultProxyConfig={defaultProxyConfig}
|
||||||
clientID={clientID}
|
proxyName={proxyName}
|
||||||
clientProxyConfigs={clientProxyConfigs}
|
serverID={serverID}
|
||||||
setClientProxyConfigs={setClientProxyConfigs}
|
clientID={clientID}
|
||||||
enablePreview={enablePreview}
|
clientProxyConfigs={clientProxyConfigs}
|
||||||
/>
|
setClientProxyConfigs={setClientProxyConfigs}
|
||||||
)}
|
enablePreview={enablePreview}
|
||||||
{defaultProxyConfig.type === 'udp' && serverID && clientID && (
|
/>
|
||||||
<UDPProxyForm
|
)}
|
||||||
defaultProxyConfig={defaultProxyConfig}
|
{defaultProxyConfig.type === 'udp' && serverID && clientID && (
|
||||||
proxyName={proxyName}
|
<UDPProxyForm
|
||||||
serverID={serverID}
|
defaultProxyConfig={defaultProxyConfig}
|
||||||
clientID={clientID}
|
proxyName={proxyName}
|
||||||
clientProxyConfigs={clientProxyConfigs}
|
serverID={serverID}
|
||||||
setClientProxyConfigs={setClientProxyConfigs}
|
clientID={clientID}
|
||||||
enablePreview={enablePreview}
|
clientProxyConfigs={clientProxyConfigs}
|
||||||
/>
|
setClientProxyConfigs={setClientProxyConfigs}
|
||||||
)}
|
enablePreview={enablePreview}
|
||||||
{defaultProxyConfig.type === 'http' && serverID && clientID && (
|
/>
|
||||||
<HTTPProxyForm
|
)}
|
||||||
defaultProxyConfig={defaultProxyConfig}
|
{defaultProxyConfig.type === 'http' && serverID && clientID && (
|
||||||
proxyName={proxyName}
|
<HTTPProxyForm
|
||||||
serverID={serverID}
|
defaultProxyConfig={defaultProxyConfig}
|
||||||
clientID={clientID}
|
proxyName={proxyName}
|
||||||
clientProxyConfigs={clientProxyConfigs}
|
serverID={serverID}
|
||||||
setClientProxyConfigs={setClientProxyConfigs}
|
clientID={clientID}
|
||||||
enablePreview={enablePreview}
|
clientProxyConfigs={clientProxyConfigs}
|
||||||
/>
|
setClientProxyConfigs={setClientProxyConfigs}
|
||||||
)}
|
enablePreview={enablePreview}
|
||||||
{defaultProxyConfig.type === 'stcp' && serverID && clientID && (
|
/>
|
||||||
<STCPProxyForm
|
)}
|
||||||
defaultProxyConfig={defaultProxyConfig}
|
{defaultProxyConfig.type === 'stcp' && serverID && clientID && (
|
||||||
proxyName={proxyName}
|
<STCPProxyForm
|
||||||
serverID={serverID}
|
defaultProxyConfig={defaultProxyConfig}
|
||||||
clientID={clientID}
|
proxyName={proxyName}
|
||||||
clientProxyConfigs={clientProxyConfigs}
|
serverID={serverID}
|
||||||
setClientProxyConfigs={setClientProxyConfigs}
|
clientID={clientID}
|
||||||
enablePreview={enablePreview}
|
clientProxyConfigs={clientProxyConfigs}
|
||||||
/>
|
setClientProxyConfigs={setClientProxyConfigs}
|
||||||
)}</>)
|
enablePreview={enablePreview}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs, enablePreview }) => {
|
export const TCPProxyForm: React.FC<ProxyFormProps> = ({
|
||||||
|
serverID,
|
||||||
|
clientID,
|
||||||
|
defaultProxyConfig,
|
||||||
|
proxyName,
|
||||||
|
clientProxyConfigs,
|
||||||
|
setClientProxyConfigs,
|
||||||
|
enablePreview,
|
||||||
|
}) => {
|
||||||
const defaultConfig = defaultProxyConfig as TCPProxyConfig
|
const defaultConfig = defaultProxyConfig as TCPProxyConfig
|
||||||
const [_, setTCPConfig] = useState<TCPProxyConfig | undefined>()
|
|
||||||
const [timeoutID, setTimeoutID] = useState<NodeJS.Timeout | undefined>()
|
const [timeoutID, setTimeoutID] = useState<NodeJS.Timeout | undefined>()
|
||||||
const form = useForm<z.infer<typeof TCPConfigSchema>>({
|
const form = useForm<z.infer<typeof TCPConfigSchema>>({
|
||||||
resolver: zodResolver(TCPConfigSchema),
|
resolver: zodResolver(TCPConfigSchema),
|
||||||
@@ -122,19 +137,27 @@ export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
remotePort: defaultConfig?.remotePort,
|
remotePort: defaultConfig?.remotePort,
|
||||||
localIP: defaultConfig?.localIP,
|
localIP: defaultConfig?.localIP,
|
||||||
localPort: defaultConfig?.localPort,
|
localPort: defaultConfig?.localPort,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [usePlugin, setUsePlugin] = useState<boolean>(
|
||||||
|
(defaultConfig.plugin && defaultConfig.plugin.type.length > 0) || false,
|
||||||
|
)
|
||||||
|
const [pluginConfig, setPluginConfig] = useState<TypedClientPluginOptions | undefined>(defaultConfig.plugin)
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof TCPConfigSchema>) => {
|
const onSubmit = async (values: z.infer<typeof TCPConfigSchema>) => {
|
||||||
|
const cfgToSubmit = { ...values, plugin: pluginConfig, type: 'tcp', name: proxyName } as TCPProxyConfig
|
||||||
|
if (!TypedProxyConfigValid(cfgToSubmit)) {
|
||||||
|
toast.error('Invalid configuration')
|
||||||
|
return
|
||||||
|
}
|
||||||
handleSave()
|
handleSave()
|
||||||
setTCPConfig({ type: 'tcp', ...values, name: proxyName })
|
|
||||||
const newProxiyConfigs = clientProxyConfigs.map((proxyCfg) => {
|
const newProxiyConfigs = clientProxyConfigs.map((proxyCfg) => {
|
||||||
if (proxyCfg.name === proxyName) {
|
if (proxyCfg.name === proxyName) {
|
||||||
return { ...values, type: 'tcp', name: proxyName } as TCPProxyConfig
|
return cfgToSubmit
|
||||||
}
|
}
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
})
|
})
|
||||||
console.log('newProxiyConfigs', newProxiyConfigs)
|
|
||||||
setClientProxyConfigs(newProxiyConfigs)
|
setClientProxyConfigs(newProxiyConfigs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,9 +168,11 @@ export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
if (timeoutID) {
|
if (timeoutID) {
|
||||||
clearTimeout(timeoutID)
|
clearTimeout(timeoutID)
|
||||||
}
|
}
|
||||||
setTimeoutID(setTimeout(() => {
|
setTimeoutID(
|
||||||
setSaveDisabled(false)
|
setTimeout(() => {
|
||||||
}, 3000))
|
setSaveDisabled(false)
|
||||||
|
}, 3000),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -162,18 +187,36 @@ export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-0.5">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-0.5">
|
||||||
{server?.server?.ip && defaultConfig.remotePort && defaultConfig.localIP && defaultConfig.localPort && enablePreview && (
|
{server?.server?.ip &&
|
||||||
<div className="flex items-center space-x-2 flex-col justify-start w-full">
|
defaultConfig.remotePort &&
|
||||||
<Label className="text-sm font-medium text-start w-full">{t('proxy.form.access_method')}</Label>
|
defaultConfig.localIP &&
|
||||||
<div className='w-full justify-start overflow-x-scroll'>
|
defaultConfig.localPort &&
|
||||||
<VisitPreview server={server?.server} typedProxyConfig={defaultConfig} />
|
enablePreview && (
|
||||||
|
<div className="flex items-center space-x-2 flex-col justify-start w-full">
|
||||||
|
<Label className="text-sm font-medium text-start w-full">{t('proxy.form.access_method')}</Label>
|
||||||
|
<div className="w-full justify-start overflow-x-scroll">
|
||||||
|
<VisitPreview server={server?.server} typedProxyConfig={defaultConfig} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
<PortField name="localPort" control={form.control} label={t('proxy.form.local_port') + '*'} />
|
||||||
<PortField name="localPort" control={form.control} label={t('proxy.form.local_port') + "*"} />
|
<HostField name="localIP" control={form.control} label={t('proxy.form.local_ip') + '*'} />
|
||||||
<HostField name="localIP" control={form.control} label={t('proxy.form.local_ip') + "*"} />
|
<PortField
|
||||||
<PortField name="remotePort" control={form.control} label={t('proxy.form.remote_port') + "*"} placeholder='4321' />
|
name="remotePort"
|
||||||
<Button type="submit" disabled={isSaveDisabled} variant={'outline'} className='w-full'>
|
control={form.control}
|
||||||
|
label={t('proxy.form.remote_port') + '*'}
|
||||||
|
placeholder="4321"
|
||||||
|
/>
|
||||||
|
<SwitchWithLabel
|
||||||
|
name="usePlugin"
|
||||||
|
label={t('proxy.form.use_plugin')}
|
||||||
|
defaultValue={usePlugin}
|
||||||
|
setValue={setUsePlugin}
|
||||||
|
/>
|
||||||
|
{usePlugin ? (
|
||||||
|
<PluginConfigForm defaultPluginConfig={defaultConfig.plugin} setPluginConfig={setPluginConfig} />
|
||||||
|
) : null}
|
||||||
|
<Button type="submit" disabled={isSaveDisabled} variant={'outline'} className="w-full">
|
||||||
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
|
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
|
||||||
{t('proxy.form.save_changes')}
|
{t('proxy.form.save_changes')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -182,9 +225,16 @@ export const TCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const STCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs, enablePreview }) => {
|
export const STCPProxyForm: React.FC<ProxyFormProps> = ({
|
||||||
|
serverID,
|
||||||
|
clientID,
|
||||||
|
defaultProxyConfig,
|
||||||
|
proxyName,
|
||||||
|
clientProxyConfigs,
|
||||||
|
setClientProxyConfigs,
|
||||||
|
enablePreview,
|
||||||
|
}) => {
|
||||||
const defaultConfig = defaultProxyConfig as STCPProxyConfig
|
const defaultConfig = defaultProxyConfig as STCPProxyConfig
|
||||||
const [_, setSTCPConfig] = useState<STCPProxyConfig | undefined>()
|
|
||||||
const [timeoutID, setTimeoutID] = useState<NodeJS.Timeout | undefined>()
|
const [timeoutID, setTimeoutID] = useState<NodeJS.Timeout | undefined>()
|
||||||
const form = useForm<z.infer<typeof STCPConfigSchema>>({
|
const form = useForm<z.infer<typeof STCPConfigSchema>>({
|
||||||
resolver: zodResolver(STCPConfigSchema),
|
resolver: zodResolver(STCPConfigSchema),
|
||||||
@@ -192,15 +242,25 @@ export const STCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
|
|||||||
localPort: defaultConfig?.localPort,
|
localPort: defaultConfig?.localPort,
|
||||||
localIP: defaultConfig?.localIP,
|
localIP: defaultConfig?.localIP,
|
||||||
secretKey: defaultConfig?.secretKey,
|
secretKey: defaultConfig?.secretKey,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [usePlugin, setUsePlugin] = useState<boolean>(
|
||||||
|
(defaultConfig.plugin && defaultConfig.plugin.type.length > 0) || false,
|
||||||
|
)
|
||||||
|
|
||||||
|
const [pluginConfig, setPluginConfig] = useState<TypedClientPluginOptions | undefined>(defaultConfig.plugin)
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof STCPConfigSchema>) => {
|
const onSubmit = async (values: z.infer<typeof STCPConfigSchema>) => {
|
||||||
|
const cfgToSubmit = { ...values, plugin: pluginConfig, type: 'stcp', name: proxyName } as STCPProxyConfig
|
||||||
|
if (!TypedProxyConfigValid(cfgToSubmit)) {
|
||||||
|
toast.error('Invalid configuration')
|
||||||
|
return
|
||||||
|
}
|
||||||
handleSave()
|
handleSave()
|
||||||
setSTCPConfig({ type: 'stcp', ...values, name: proxyName })
|
|
||||||
const newProxiyConfigs = clientProxyConfigs.map((proxyCfg) => {
|
const newProxiyConfigs = clientProxyConfigs.map((proxyCfg) => {
|
||||||
if (proxyCfg.name === proxyName) {
|
if (proxyCfg.name === proxyName) {
|
||||||
return { ...values, type: 'stcp', name: proxyName } as STCPProxyConfig
|
return cfgToSubmit
|
||||||
}
|
}
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
})
|
})
|
||||||
@@ -214,9 +274,11 @@ export const STCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
|
|||||||
if (timeoutID) {
|
if (timeoutID) {
|
||||||
clearTimeout(timeoutID)
|
clearTimeout(timeoutID)
|
||||||
}
|
}
|
||||||
setTimeoutID(setTimeout(() => {
|
setTimeoutID(
|
||||||
setSaveDisabled(false)
|
setTimeout(() => {
|
||||||
}, 3000))
|
setSaveDisabled(false)
|
||||||
|
}, 3000),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -224,10 +286,19 @@ export const STCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
|
|||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-0.5">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-0.5">
|
||||||
<PortField name="localPort" control={form.control} label={t('proxy.form.local_port') + "*"} />
|
<PortField name="localPort" control={form.control} label={t('proxy.form.local_port') + '*'} />
|
||||||
<HostField name="localIP" control={form.control} label={t('proxy.form.local_ip') + "*"} />
|
<HostField name="localIP" control={form.control} label={t('proxy.form.local_ip') + '*'} />
|
||||||
<SecretStringField name="secretKey" control={form.control} label={t('proxy.form.secret_key') + "*"} />
|
<SwitchWithLabel
|
||||||
<Button type="submit" disabled={isSaveDisabled} variant={'outline'} className='w-full'>
|
name="usePlugin"
|
||||||
|
defaultValue={usePlugin}
|
||||||
|
setValue={setUsePlugin}
|
||||||
|
label={t('proxy.form.use_plugin')}
|
||||||
|
/>
|
||||||
|
{usePlugin ? (
|
||||||
|
<PluginConfigForm defaultPluginConfig={defaultConfig.plugin} setPluginConfig={setPluginConfig} />
|
||||||
|
) : null}
|
||||||
|
<SecretStringField name="secretKey" control={form.control} label={t('proxy.form.secret_key') + '*'} />
|
||||||
|
<Button type="submit" disabled={isSaveDisabled} variant={'outline'} className="w-full">
|
||||||
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
|
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
|
||||||
{t('proxy.form.save_changes')}
|
{t('proxy.form.save_changes')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -236,9 +307,16 @@ export const STCPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs, enablePreview }) => {
|
export const UDPProxyForm: React.FC<ProxyFormProps> = ({
|
||||||
|
serverID,
|
||||||
|
clientID,
|
||||||
|
defaultProxyConfig,
|
||||||
|
proxyName,
|
||||||
|
clientProxyConfigs,
|
||||||
|
setClientProxyConfigs,
|
||||||
|
enablePreview,
|
||||||
|
}) => {
|
||||||
const defaultConfig = defaultProxyConfig as UDPProxyConfig
|
const defaultConfig = defaultProxyConfig as UDPProxyConfig
|
||||||
const [_, setUDPConfig] = useState<UDPProxyConfig | undefined>()
|
|
||||||
const [timeoutID, setTimeoutID] = useState<NodeJS.Timeout | undefined>()
|
const [timeoutID, setTimeoutID] = useState<NodeJS.Timeout | undefined>()
|
||||||
const form = useForm<z.infer<typeof UDPConfigSchema>>({
|
const form = useForm<z.infer<typeof UDPConfigSchema>>({
|
||||||
resolver: zodResolver(UDPConfigSchema),
|
resolver: zodResolver(UDPConfigSchema),
|
||||||
@@ -246,15 +324,25 @@ export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
localPort: defaultConfig?.localPort,
|
localPort: defaultConfig?.localPort,
|
||||||
localIP: defaultConfig?.localIP,
|
localIP: defaultConfig?.localIP,
|
||||||
remotePort: defaultConfig?.remotePort,
|
remotePort: defaultConfig?.remotePort,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [usePlugin, setUsePlugin] = useState<boolean>(
|
||||||
|
(defaultConfig.plugin && defaultConfig.plugin.type.length > 0) || false,
|
||||||
|
)
|
||||||
|
|
||||||
|
const [pluginConfig, setPluginConfig] = useState<TypedClientPluginOptions | undefined>(defaultConfig.plugin)
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof UDPConfigSchema>) => {
|
const onSubmit = async (values: z.infer<typeof UDPConfigSchema>) => {
|
||||||
|
const cfgToSubmit = { ...values, plugin: pluginConfig, type: 'udp', name: proxyName } as UDPProxyConfig
|
||||||
|
if (!TypedProxyConfigValid(cfgToSubmit)) {
|
||||||
|
toast.error('Invalid configuration')
|
||||||
|
return
|
||||||
|
}
|
||||||
handleSave()
|
handleSave()
|
||||||
setUDPConfig({ type: 'udp', ...values, name: proxyName })
|
|
||||||
const newProxiyConfigs = clientProxyConfigs.map((proxyCfg) => {
|
const newProxiyConfigs = clientProxyConfigs.map((proxyCfg) => {
|
||||||
if (proxyCfg.name === proxyName) {
|
if (proxyCfg.name === proxyName) {
|
||||||
return { ...values, type: 'udp', name: proxyName } as UDPProxyConfig
|
return cfgToSubmit
|
||||||
}
|
}
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
})
|
})
|
||||||
@@ -268,9 +356,11 @@ export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
if (timeoutID) {
|
if (timeoutID) {
|
||||||
clearTimeout(timeoutID)
|
clearTimeout(timeoutID)
|
||||||
}
|
}
|
||||||
setTimeoutID(setTimeout(() => {
|
setTimeoutID(
|
||||||
setSaveDisabled(false)
|
setTimeout(() => {
|
||||||
}, 3000))
|
setSaveDisabled(false)
|
||||||
|
}, 3000),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -285,18 +375,31 @@ export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-0.5">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-0.5">
|
||||||
{server?.server?.ip && defaultConfig.remotePort && defaultConfig.localIP && defaultConfig.localPort && enablePreview && (
|
{server?.server?.ip &&
|
||||||
<div className="flex items-center space-x-2 flex-col justify-start w-full">
|
defaultConfig.remotePort &&
|
||||||
<Label className="text-sm font-medium text-start w-full">{t('proxy.form.access_method')}</Label>
|
defaultConfig.localIP &&
|
||||||
<div className='w-full justify-start overflow-x-scroll'>
|
defaultConfig.localPort &&
|
||||||
<VisitPreview server={server?.server} typedProxyConfig={defaultConfig} />
|
enablePreview && (
|
||||||
|
<div className="flex items-center space-x-2 flex-col justify-start w-full">
|
||||||
|
<Label className="text-sm font-medium text-start w-full">{t('proxy.form.access_method')}</Label>
|
||||||
|
<div className="w-full justify-start overflow-x-scroll">
|
||||||
|
<VisitPreview server={server?.server} typedProxyConfig={defaultConfig} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
<PortField name="localPort" control={form.control} label={t('proxy.form.local_port') + '*'} />
|
||||||
<PortField name="localPort" control={form.control} label={t('proxy.form.local_port') + "*"} />
|
<HostField name="localIP" control={form.control} label={t('proxy.form.local_ip') + '*'} />
|
||||||
<HostField name="localIP" control={form.control} label={t('proxy.form.local_ip') + "*"} />
|
<PortField name="remotePort" control={form.control} label={t('proxy.form.remote_port') + '*'} />
|
||||||
<PortField name="remotePort" control={form.control} label={t('proxy.form.remote_port') + "*"} />
|
<SwitchWithLabel
|
||||||
<Button type="submit" disabled={isSaveDisabled} variant={'outline'} className='w-full'>
|
name="usePlugin"
|
||||||
|
defaultValue={usePlugin}
|
||||||
|
setValue={setUsePlugin}
|
||||||
|
label={t('proxy.form.use_plugin')}
|
||||||
|
/>
|
||||||
|
{usePlugin ? (
|
||||||
|
<PluginConfigForm defaultPluginConfig={defaultConfig.plugin} setPluginConfig={setPluginConfig} />
|
||||||
|
) : null}
|
||||||
|
<Button type="submit" disabled={isSaveDisabled} variant={'outline'} className="w-full">
|
||||||
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
|
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
|
||||||
{t('proxy.form.save_changes')}
|
{t('proxy.form.save_changes')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -305,9 +408,16 @@ export const UDPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, def
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, defaultProxyConfig, proxyName, clientProxyConfigs, setClientProxyConfigs, enablePreview }) => {
|
export const HTTPProxyForm: React.FC<ProxyFormProps> = ({
|
||||||
|
serverID,
|
||||||
|
clientID,
|
||||||
|
defaultProxyConfig,
|
||||||
|
proxyName,
|
||||||
|
clientProxyConfigs,
|
||||||
|
setClientProxyConfigs,
|
||||||
|
enablePreview,
|
||||||
|
}) => {
|
||||||
const defaultConfig = defaultProxyConfig as HTTPProxyConfig
|
const defaultConfig = defaultProxyConfig as HTTPProxyConfig
|
||||||
const [_, setHTTPConfig] = useState<HTTPProxyConfig | undefined>()
|
|
||||||
const [timeoutID, setTimeoutID] = useState<NodeJS.Timeout | undefined>()
|
const [timeoutID, setTimeoutID] = useState<NodeJS.Timeout | undefined>()
|
||||||
const [moreSettings, setMoreSettings] = useState(false)
|
const [moreSettings, setMoreSettings] = useState(false)
|
||||||
const [useAuth, setUseAuth] = useState(false)
|
const [useAuth, setUseAuth] = useState(false)
|
||||||
@@ -321,16 +431,30 @@ export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
|
|||||||
locations: defaultConfig?.locations,
|
locations: defaultConfig?.locations,
|
||||||
customDomains: defaultConfig?.customDomains,
|
customDomains: defaultConfig?.customDomains,
|
||||||
httpPassword: defaultConfig?.httpPassword,
|
httpPassword: defaultConfig?.httpPassword,
|
||||||
httpUser: defaultConfig?.httpUser
|
httpUser: defaultConfig?.httpUser,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [usePlugin, setUsePlugin] = useState<boolean>(
|
||||||
|
(defaultConfig.plugin && defaultConfig.plugin.type.length > 0) || false,
|
||||||
|
)
|
||||||
|
|
||||||
|
const [pluginConfig, setPluginConfig] = useState<TypedClientPluginOptions | undefined>(defaultConfig.plugin)
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof HTTPConfigSchema>) => {
|
const onSubmit = async (values: z.infer<typeof HTTPConfigSchema>) => {
|
||||||
|
const cfgToSubmit = { ...values, plugin: pluginConfig, type: 'http', name: proxyName } as HTTPProxyConfig
|
||||||
|
if (!TypedProxyConfigValid(cfgToSubmit)) {
|
||||||
|
toast.error('Invalid configuration')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!values.customDomains && !values.subdomain) {
|
||||||
|
toast.error('Please provide a subdomain or custom domains')
|
||||||
|
return
|
||||||
|
}
|
||||||
handleSave()
|
handleSave()
|
||||||
setHTTPConfig({ ...values, type: 'http', name: proxyName })
|
|
||||||
const newProxiyConfigs = clientProxyConfigs.map((proxyCfg) => {
|
const newProxiyConfigs = clientProxyConfigs.map((proxyCfg) => {
|
||||||
if (proxyCfg.name === proxyName) {
|
if (proxyCfg.name === proxyName) {
|
||||||
return { ...values, type: 'http', name: proxyName } as HTTPProxyConfig
|
return cfgToSubmit
|
||||||
}
|
}
|
||||||
return proxyCfg
|
return proxyCfg
|
||||||
})
|
})
|
||||||
@@ -350,9 +474,11 @@ export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
|
|||||||
if (timeoutID) {
|
if (timeoutID) {
|
||||||
clearTimeout(timeoutID)
|
clearTimeout(timeoutID)
|
||||||
}
|
}
|
||||||
setTimeoutID(setTimeout(() => {
|
setTimeoutID(
|
||||||
setSaveDisabled(false)
|
setTimeout(() => {
|
||||||
}, 3000))
|
setSaveDisabled(false)
|
||||||
|
}, 3000),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -367,38 +493,84 @@ export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
|
|||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-0.5">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 px-0.5">
|
||||||
{server && server.server && server.server.ip && defaultConfig &&
|
{server &&
|
||||||
defaultConfig.localIP && defaultConfig.localPort &&
|
server.server &&
|
||||||
defaultConfig.subdomain
|
server.server.ip &&
|
||||||
&& enablePreview && <div className="flex items-center space-x-2 flex-col justify-start w-full">
|
defaultConfig &&
|
||||||
<Label className="text-sm font-medium text-start w-full">{t('proxy.form.access_method')}</Label>
|
defaultConfig.localIP &&
|
||||||
<div className='w-full justify-start overflow-x-scroll'>
|
defaultConfig.localPort &&
|
||||||
<VisitPreview server={server?.server} typedProxyConfig={defaultConfig} />
|
defaultConfig.subdomain &&
|
||||||
|
enablePreview && (
|
||||||
|
<div className="flex items-center space-x-2 flex-col justify-start w-full">
|
||||||
|
<Label className="text-sm font-medium text-start w-full">{t('proxy.form.access_method')}</Label>
|
||||||
|
<div className="w-full justify-start overflow-x-scroll">
|
||||||
|
<VisitPreview server={server?.server} typedProxyConfig={defaultConfig} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
)}
|
||||||
<PortField name="localPort" control={form.control} label={t('proxy.form.local_port') + "*"} />
|
<PortField name="localPort" control={form.control} label={t('proxy.form.local_port') + '*'} />
|
||||||
<HostField name="localIP" control={form.control} label={t('proxy.form.local_ip') + "*"} />
|
<HostField name="localIP" control={form.control} label={t('proxy.form.local_ip') + '*'} />
|
||||||
<StringField name="subdomain" control={form.control} label={t('proxy.form.subdomain')} placeholder={"your_sub_domain"} />
|
<StringField
|
||||||
<StringArrayField name="customDomains" control={form.control} label={t('proxy.form.custom_domains')} placeholder={"your.example.com"} />
|
name="subdomain"
|
||||||
<FormDescription>
|
control={form.control}
|
||||||
{t('proxy.form.domain_description')}
|
label={t('proxy.form.subdomain')}
|
||||||
</FormDescription>
|
placeholder={'your_sub_domain'}
|
||||||
<div className="flex items-center space-x-2 justify-between">
|
/>
|
||||||
<Label htmlFor="more-settings">{t('proxy.form.more_settings')}</Label>
|
<StringArrayField
|
||||||
<Switch id="more-settings" checked={moreSettings} onCheckedChange={setMoreSettings} />
|
name="customDomains"
|
||||||
</div>
|
control={form.control}
|
||||||
{moreSettings && <div className='p-4 space-y-4 border rounded-md'>
|
label={t('proxy.form.custom_domains')}
|
||||||
<StringArrayField name="locations" control={form.control} label={t('proxy.form.route')} placeholder={"/path"} />
|
placeholder={'your.example.com'}
|
||||||
<div className="flex items-center space-x-2 justify-between">
|
/>
|
||||||
<Label htmlFor="enable-http-auth">{t('proxy.form.enable_http_auth')}</Label>
|
<FormDescription>{t('proxy.form.domain_description')}</FormDescription>
|
||||||
<Switch id="enable-http-auth" checked={useAuth} onCheckedChange={setUseAuth} />
|
<SwitchWithLabel
|
||||||
|
name="usePlugin"
|
||||||
|
defaultValue={usePlugin}
|
||||||
|
setValue={setUsePlugin}
|
||||||
|
label={t('proxy.form.use_plugin')}
|
||||||
|
/>
|
||||||
|
{usePlugin ? (
|
||||||
|
<PluginConfigForm defaultPluginConfig={defaultConfig.plugin} setPluginConfig={setPluginConfig} />
|
||||||
|
) : null}
|
||||||
|
<SwitchWithLabel
|
||||||
|
name="moreSettings"
|
||||||
|
label={t('proxy.form.more_settings')}
|
||||||
|
defaultValue={moreSettings}
|
||||||
|
setValue={setMoreSettings}
|
||||||
|
/>
|
||||||
|
{moreSettings && (
|
||||||
|
<div className="p-4 space-y-4 border rounded-md">
|
||||||
|
<StringArrayField
|
||||||
|
name="locations"
|
||||||
|
control={form.control}
|
||||||
|
label={t('proxy.form.route')}
|
||||||
|
placeholder={'/path'}
|
||||||
|
/>
|
||||||
|
<SwitchWithLabel
|
||||||
|
name="enableHttpAuth"
|
||||||
|
label={t('proxy.form.enable_http_auth')}
|
||||||
|
defaultValue={useAuth}
|
||||||
|
setValue={setUseAuth}
|
||||||
|
/>
|
||||||
|
{useAuth && (
|
||||||
|
<div className="p-4 space-y-4 border rounded-md">
|
||||||
|
<StringField
|
||||||
|
name="httpUser"
|
||||||
|
control={form.control}
|
||||||
|
label={t('proxy.form.username')}
|
||||||
|
placeholder={'username'}
|
||||||
|
/>
|
||||||
|
<StringField
|
||||||
|
name="httpPassword"
|
||||||
|
control={form.control}
|
||||||
|
label={t('proxy.form.password')}
|
||||||
|
placeholder={'password'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{useAuth && <div className='p-4 space-y-4 border rounded-md'>
|
)}
|
||||||
<StringField name="httpUser" control={form.control} label={t('proxy.form.username')} placeholder={"username"} />
|
<Button type="submit" disabled={isSaveDisabled} variant={'outline'} className="w-full">
|
||||||
<StringField name="httpPassword" control={form.control} label={t('proxy.form.password')} placeholder={"password"} />
|
|
||||||
</div>}
|
|
||||||
</div>}
|
|
||||||
<Button type="submit" disabled={isSaveDisabled} variant={'outline'} className='w-full'>
|
|
||||||
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
|
<YesIcon className={`mr-2 h-4 w-4 ${isSaveDisabled ? '' : 'hidden'}`}></YesIcon>
|
||||||
{t('proxy.form.save_changes')}
|
{t('proxy.form.save_changes')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -406,3 +578,23 @@ export const HTTPProxyForm: React.FC<ProxyFormProps> = ({ serverID, clientID, de
|
|||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SwitchWithLabel = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
defaultValue,
|
||||||
|
setValue,
|
||||||
|
}: {
|
||||||
|
name: string
|
||||||
|
label: string
|
||||||
|
defaultValue?: boolean
|
||||||
|
setValue: (value: boolean) => void
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<div className="flex items-center space-x-2 justify-between">
|
||||||
|
<Label htmlFor={name}>{t(label)}</Label>
|
||||||
|
<Switch id={`switch-with-label-${name}-switch`} checked={defaultValue} onCheckedChange={setValue} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@@ -15,7 +15,7 @@ export const Header = ({ title }: { title?: string }) => {
|
|||||||
const token = useStore($token)
|
const token = useStore($token)
|
||||||
const currentPath = router.pathname
|
const currentPath = router.pathname
|
||||||
|
|
||||||
const {isPending} = useQuery({
|
const { isPending } = useQuery({
|
||||||
queryKey: ['userInfo', currentPath],
|
queryKey: ['userInfo', currentPath],
|
||||||
queryFn: getUserInfo,
|
queryFn: getUserInfo,
|
||||||
retry: false,
|
retry: false,
|
||||||
@@ -24,7 +24,7 @@ export const Header = ({ title }: { title?: string }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 只有在初始化完成后才进行状态检查和跳转
|
// 只有在初始化完成后才进行状态检查和跳转
|
||||||
if (!isPending) {
|
if (!isPending) {
|
||||||
console.log('isInitializing', isOnline, token, currentPath)
|
// console.log('isInitializing', isOnline, token, currentPath)
|
||||||
// 如果用户未登录且不在登录/注册页面,则跳转到登录页
|
// 如果用户未登录且不在登录/注册页面,则跳转到登录页
|
||||||
const isAuthPage = ['/login', '/register'].includes(currentPath)
|
const isAuthPage = ['/login', '/register'].includes(currentPath)
|
||||||
if ((!token || !isOnline) && !isAuthPage) {
|
if ((!token || !isOnline) && !isAuthPage) {
|
||||||
@@ -35,7 +35,7 @@ export const Header = ({ title }: { title?: string }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full justify-between items-center gap-2">
|
<div className="flex w-full justify-between items-center gap-2">
|
||||||
{title && <p className='font-bold'>{title}</p>}
|
{title && <p className="font-bold">{title}</p>}
|
||||||
{!title && <p></p>}
|
{!title && <p></p>}
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
</div>
|
</div>
|
||||||
@@ -56,7 +56,7 @@ export const RegisterAndLogin = () => {
|
|||||||
$platformInfo.set(platformInfo.data)
|
$platformInfo.set(platformInfo.data)
|
||||||
}, [platformInfo])
|
}, [platformInfo])
|
||||||
|
|
||||||
const {data: userInfoQuery} = useQuery({
|
const { data: userInfoQuery } = useQuery({
|
||||||
queryKey: ['userInfo'],
|
queryKey: ['userInfo'],
|
||||||
queryFn: getUserInfo,
|
queryFn: getUserInfo,
|
||||||
})
|
})
|
||||||
@@ -80,4 +80,4 @@ export const RegisterAndLogin = () => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { ProxyType, TypedProxyConfig } from "@/types/proxy"
|
import { ProxyType, TypedProxyConfig } from '@/types/proxy'
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Button } from "../ui/button"
|
import { Button } from '../ui/button'
|
||||||
import { BaseDropdownMenu } from "../base/drop-down-menu"
|
import { BaseDropdownMenu } from '../base/drop-down-menu'
|
||||||
import { deleteProxyConfig } from "@/api/proxy"
|
import { deleteProxyConfig, startProxy, stopProxy } from '@/api/proxy'
|
||||||
import { useMutation } from "@tanstack/react-query"
|
import { useMutation } from '@tanstack/react-query'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogClose,
|
DialogClose,
|
||||||
@@ -13,13 +13,14 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog'
|
} from '@/components/ui/dialog'
|
||||||
import { ProxyConfigMutateForm } from "./mutate_proxy_config"
|
import { ProxyConfigMutateForm } from './mutate_proxy_config'
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { Row } from "@tanstack/react-table"
|
import { Row } from '@tanstack/react-table'
|
||||||
import { ProxyConfigTableSchema } from "./proxy_config_item"
|
import { ProxyConfigTableSchema } from './proxy_config_item'
|
||||||
import { MoreHorizontal } from "lucide-react"
|
import { MoreHorizontal } from 'lucide-react'
|
||||||
import { toast } from "sonner"
|
import { toast } from 'sonner'
|
||||||
import { $proxyTableRefetchTrigger } from "@/store/refetch-trigger"
|
import { $proxyTableRefetchTrigger } from '@/store/refetch-trigger'
|
||||||
|
import { DropdownMenuGroup, DropdownMenuItem } from '../ui/dropdown-menu'
|
||||||
|
|
||||||
export interface ProxyConfigActionsProps {
|
export interface ProxyConfigActionsProps {
|
||||||
serverID: string
|
serverID: string
|
||||||
@@ -32,14 +33,16 @@ export function ProxyConfigActions({ serverID, clientID, name, row }: ProxyConfi
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [proxyMutateFormOpen, setProxyMutateFormOpen] = useState(false)
|
const [proxyMutateFormOpen, setProxyMutateFormOpen] = useState(false)
|
||||||
const [deleteWarnDialogOpen, setDeleteWarnDialogOpen] = useState(false)
|
const [deleteWarnDialogOpen, setDeleteWarnDialogOpen] = useState(false)
|
||||||
|
const proxyConfig = row.original
|
||||||
|
|
||||||
const deleteProxyConfigMutation = useMutation({
|
const deleteProxyConfigMutation = useMutation({
|
||||||
mutationKey: ['deleteProxyConfig', serverID, clientID, name],
|
mutationKey: ['deleteProxyConfig', serverID, clientID, name],
|
||||||
mutationFn: () => deleteProxyConfig({
|
mutationFn: () =>
|
||||||
serverId: serverID,
|
deleteProxyConfig({
|
||||||
clientId: clientID,
|
serverId: serverID,
|
||||||
name,
|
clientId: clientID,
|
||||||
}),
|
name,
|
||||||
|
}),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast(t('proxy.action.delete_success'))
|
toast(t('proxy.action.delete_success'))
|
||||||
$proxyTableRefetchTrigger.set(Math.random())
|
$proxyTableRefetchTrigger.set(Math.random())
|
||||||
@@ -52,51 +55,129 @@ export function ProxyConfigActions({ serverID, clientID, name, row }: ProxyConfi
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const menuActions = [[
|
const stopProxyMutation = useMutation({
|
||||||
{
|
mutationKey: ['stopProxy', serverID, clientID, name],
|
||||||
name: t('proxy.action.edit'),
|
mutationFn: () =>
|
||||||
onClick: () => { setProxyMutateFormOpen(true) },
|
stopProxy({
|
||||||
|
serverId: serverID,
|
||||||
|
clientId: clientID,
|
||||||
|
name,
|
||||||
|
}),
|
||||||
|
onSuccess: () => {
|
||||||
|
toast(t('proxy.action.stop_success'))
|
||||||
|
$proxyTableRefetchTrigger.set(Math.random())
|
||||||
},
|
},
|
||||||
{
|
onError: (e) => {
|
||||||
name: t('proxy.action.delete'),
|
toast(t('proxy.action.stop_failed'), {
|
||||||
onClick: () => { setDeleteWarnDialogOpen(true) },
|
description: JSON.stringify(e),
|
||||||
className: 'text-destructive',
|
})
|
||||||
|
$proxyTableRefetchTrigger.set(Math.random())
|
||||||
},
|
},
|
||||||
]]
|
})
|
||||||
return (<>
|
|
||||||
<Dialog open={proxyMutateFormOpen} onOpenChange={setProxyMutateFormOpen}>
|
const startProxyMutation = useMutation({
|
||||||
<DialogContent className="max-h-screen overflow-auto">
|
mutationKey: ['startProxy', serverID, clientID, name],
|
||||||
<ProxyConfigMutateForm
|
mutationFn: () =>
|
||||||
disableChangeProxyName
|
startProxy({
|
||||||
defaultProxyConfig={JSON.parse(row.original.config || '{}') as TypedProxyConfig}
|
serverId: serverID,
|
||||||
overwrite={true}
|
clientId: clientID,
|
||||||
defaultOriginalProxyConfig={row.original.originalProxyConfig}
|
name,
|
||||||
/>
|
}),
|
||||||
</DialogContent>
|
onSuccess: () => {
|
||||||
</Dialog>
|
toast(t('proxy.action.start_success'))
|
||||||
<Dialog open={deleteWarnDialogOpen} onOpenChange={setDeleteWarnDialogOpen}>
|
$proxyTableRefetchTrigger.set(Math.random())
|
||||||
<DialogContent>
|
},
|
||||||
<DialogHeader>
|
onError: (e) => {
|
||||||
<DialogTitle>{t('proxy.action.delete_tunnel')}</DialogTitle>
|
toast(t('proxy.action.start_failed'), {
|
||||||
<DialogDescription>
|
description: JSON.stringify(e),
|
||||||
<p className="text-destructive">{t('proxy.action.delete_attention_title')}</p>
|
})
|
||||||
<p className="text-gray-500 border-l-4 border-gray-500 pl-4 py-2">
|
$proxyTableRefetchTrigger.set(Math.random())
|
||||||
{t('proxy.action.delete_attention_description')}
|
},
|
||||||
</p>
|
})
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
const menuActions = [
|
||||||
<DialogFooter><DialogClose asChild><Button type="submit" onClick={() => {
|
[
|
||||||
deleteProxyConfigMutation.mutate()
|
{
|
||||||
}}>
|
name: t('proxy.action.edit'),
|
||||||
{t('proxy.action.delete_attention_confirm')}
|
onClick: () => {
|
||||||
</Button></DialogClose></DialogFooter>
|
setProxyMutateFormOpen(true)
|
||||||
</DialogContent>
|
},
|
||||||
</Dialog>
|
},
|
||||||
<BaseDropdownMenu
|
{
|
||||||
menuGroup={menuActions}
|
name: t('proxy.action.delete'),
|
||||||
title={t('proxy.action.title')}
|
onClick: () => {
|
||||||
trigger={<Button variant="ghost" className="h-8 w-8 p-0">
|
setDeleteWarnDialogOpen(true)
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
},
|
||||||
</Button>} />
|
className: 'text-destructive',
|
||||||
</>)
|
},
|
||||||
}
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
const extraButtons = useMemo(
|
||||||
|
() => (
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
{!proxyConfig.stopped && (
|
||||||
|
<DropdownMenuItem className="text-destructive" onClick={() => stopProxyMutation.mutate()}>
|
||||||
|
{t('client.actions_menu.pause')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
{proxyConfig.stopped && (
|
||||||
|
<DropdownMenuItem onClick={() => startProxyMutation.mutate()}>
|
||||||
|
{t('client.actions_menu.resume')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
),
|
||||||
|
[proxyConfig, startProxyMutation, stopProxyMutation, t],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog open={proxyMutateFormOpen} onOpenChange={setProxyMutateFormOpen}>
|
||||||
|
<DialogContent className="max-h-screen overflow-auto">
|
||||||
|
<ProxyConfigMutateForm
|
||||||
|
disableChangeProxyName
|
||||||
|
defaultProxyConfig={JSON.parse(row.original.config || '{}') as TypedProxyConfig}
|
||||||
|
overwrite={true}
|
||||||
|
defaultOriginalProxyConfig={row.original.originalProxyConfig}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
<Dialog open={deleteWarnDialogOpen} onOpenChange={setDeleteWarnDialogOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{t('proxy.action.delete_tunnel')}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
<p className="text-destructive">{t('proxy.action.delete_attention_title')}</p>
|
||||||
|
<p className="text-gray-500 border-l-4 border-gray-500 pl-4 py-2">
|
||||||
|
{t('proxy.action.delete_attention_description')}
|
||||||
|
</p>
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
onClick={() => {
|
||||||
|
deleteProxyConfigMutation.mutate()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('proxy.action.delete_attention_confirm')}
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
<BaseDropdownMenu
|
||||||
|
menuGroup={menuActions}
|
||||||
|
title={t('proxy.action.title')}
|
||||||
|
trigger={
|
||||||
|
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
extraButtons={extraButtons}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@@ -22,6 +22,7 @@ export type ProxyConfigTableSchema = {
|
|||||||
visitPreview: string
|
visitPreview: string
|
||||||
config?: string
|
config?: string
|
||||||
originalProxyConfig: ProxyConfig
|
originalProxyConfig: ProxyConfig
|
||||||
|
stopped: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const columns: ColumnDef<ProxyConfigTableSchema>[] = [
|
export const columns: ColumnDef<ProxyConfigTableSchema>[] = [
|
||||||
@@ -32,7 +33,7 @@ export const columns: ColumnDef<ProxyConfigTableSchema>[] = [
|
|||||||
return t('proxy.item.client_id')
|
return t('proxy.item.client_id')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return <div className='font-mono text-nowrap'>{row.original.originalProxyConfig.originClientId}</div>
|
return <div className="font-mono text-nowrap">{row.original.originalProxyConfig.originClientId}</div>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -42,7 +43,7 @@ export const columns: ColumnDef<ProxyConfigTableSchema>[] = [
|
|||||||
return t('proxy.item.proxy_name')
|
return t('proxy.item.proxy_name')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return <div className='font-mono text-nowrap'>{row.original.name}</div>
|
return <div className="font-mono text-nowrap">{row.original.name}</div>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -52,7 +53,7 @@ export const columns: ColumnDef<ProxyConfigTableSchema>[] = [
|
|||||||
return t('proxy.item.proxy_type')
|
return t('proxy.item.proxy_type')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return <div className='font-mono text-nowrap'>{row.original.type}</div>
|
return <div className="font-mono text-nowrap">{row.original.type}</div>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -62,7 +63,7 @@ export const columns: ColumnDef<ProxyConfigTableSchema>[] = [
|
|||||||
return t('proxy.item.server_id')
|
return t('proxy.item.server_id')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return <div className='font-mono text-nowrap'>{row.original.serverID}</div>
|
return <div className="font-mono text-nowrap">{row.original.serverID}</div>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -86,14 +87,16 @@ export const columns: ColumnDef<ProxyConfigTableSchema>[] = [
|
|||||||
{
|
{
|
||||||
id: 'action',
|
id: 'action',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return <ProxyConfigActions
|
return (
|
||||||
row={row}
|
<ProxyConfigActions
|
||||||
serverID={row.original.serverID}
|
row={row}
|
||||||
clientID={row.original.clientID}
|
serverID={row.original.serverID}
|
||||||
name={row.original.name}
|
clientID={row.original.clientID}
|
||||||
/>
|
name={row.original.name}
|
||||||
|
/>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
function VisitPreviewField({ row }: { row: Row<ProxyConfigTableSchema> }) {
|
function VisitPreviewField({ row }: { row: Row<ProxyConfigTableSchema> }) {
|
||||||
@@ -106,9 +109,7 @@ function VisitPreviewField({ row }: { row: Row<ProxyConfigTableSchema> }) {
|
|||||||
|
|
||||||
const typedProxyConfig = JSON.parse(row.original.config || '{}') as TypedProxyConfig
|
const typedProxyConfig = JSON.parse(row.original.config || '{}') as TypedProxyConfig
|
||||||
|
|
||||||
return <VisitPreview
|
return <VisitPreview server={server?.server || { frpsUrls: [] }} typedProxyConfig={typedProxyConfig} />
|
||||||
server={server?.server || {frpsUrls: []}}
|
|
||||||
typedProxyConfig={typedProxyConfig} />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProxyStatus({ row }: { row: Row<ProxyConfigTableSchema> }) {
|
function ProxyStatus({ row }: { row: Row<ProxyConfigTableSchema> }) {
|
||||||
@@ -119,10 +120,10 @@ function ProxyStatus({ row }: { row: Row<ProxyConfigTableSchema> }) {
|
|||||||
return getProxyConfig({
|
return getProxyConfig({
|
||||||
clientId: row.original.clientID,
|
clientId: row.original.clientID,
|
||||||
serverId: row.original.serverID,
|
serverId: row.original.serverID,
|
||||||
name: row.original.name
|
name: row.original.name,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
refetchInterval: 10000
|
refetchInterval: 10000,
|
||||||
})
|
})
|
||||||
|
|
||||||
function getStatusColor(status: string): string {
|
function getStatusColor(status: string): string {
|
||||||
@@ -130,23 +131,28 @@ function ProxyStatus({ row }: { row: Row<ProxyConfigTableSchema> }) {
|
|||||||
case 'new':
|
case 'new':
|
||||||
return 'text-blue-500'
|
return 'text-blue-500'
|
||||||
case 'wait start':
|
case 'wait start':
|
||||||
return 'text-yellow-400';
|
return 'text-yellow-400'
|
||||||
case 'start error':
|
case 'start error':
|
||||||
return 'text-red-500';
|
return 'text-red-500'
|
||||||
case 'running':
|
case 'running':
|
||||||
return 'text-green-500';
|
return 'text-green-500'
|
||||||
case 'check failed':
|
case 'check failed':
|
||||||
return 'text-orange-500';
|
return 'text-orange-500'
|
||||||
case 'error':
|
case 'error':
|
||||||
return 'text-red-600';
|
return 'text-red-600'
|
||||||
default:
|
default:
|
||||||
return 'text-gray-500';
|
return 'text-gray-500'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="flex items-center gap-2 flex-row text-nowrap">
|
return (
|
||||||
<Badge variant={"secondary"} className={`p-2 border rounded font-mono w-fit ${getStatusColor(data?.workingStatus?.status || 'unknown')} text-nowrap rounded-full h-6`}>
|
<div className="flex items-center gap-2 flex-row text-nowrap">
|
||||||
{data?.workingStatus?.status || "loading"}
|
<Badge
|
||||||
</Badge>
|
variant={'secondary'}
|
||||||
</div>
|
className={`p-2 border rounded font-mono w-fit ${getStatusColor(data?.workingStatus?.status || 'unknown')} text-nowrap rounded-full h-6`}
|
||||||
}
|
>
|
||||||
|
{data?.workingStatus?.status || 'loading'}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@@ -28,7 +28,13 @@ export interface ProxyConfigListProps {
|
|||||||
TriggerRefetch?: string
|
TriggerRefetch?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProxyConfigList: React.FC<ProxyConfigListProps> = ({ ProxyConfigs, Keyword, TriggerRefetch, ClientID, ServerID }) => {
|
export const ProxyConfigList: React.FC<ProxyConfigListProps> = ({
|
||||||
|
ProxyConfigs,
|
||||||
|
Keyword,
|
||||||
|
TriggerRefetch,
|
||||||
|
ClientID,
|
||||||
|
ServerID,
|
||||||
|
}) => {
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
|
||||||
const globalRefetchTrigger = useStore($proxyTableRefetchTrigger)
|
const globalRefetchTrigger = useStore($proxyTableRefetchTrigger)
|
||||||
@@ -41,8 +47,9 @@ export const ProxyConfigList: React.FC<ProxyConfigListProps> = ({ ProxyConfigs,
|
|||||||
serverID: proxy_config.serverId || '',
|
serverID: proxy_config.serverId || '',
|
||||||
name: proxy_config.name || '',
|
name: proxy_config.name || '',
|
||||||
type: proxy_config.type || '',
|
type: proxy_config.type || '',
|
||||||
visitPreview: "for test",
|
visitPreview: 'for test',
|
||||||
originalProxyConfig: proxy_config,
|
originalProxyConfig: proxy_config,
|
||||||
|
stopped: proxy_config.stopped,
|
||||||
}) as ProxyConfigTableSchema,
|
}) as ProxyConfigTableSchema,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -94,8 +101,9 @@ export const ProxyConfigList: React.FC<ProxyConfigListProps> = ({ ProxyConfigs,
|
|||||||
config: proxy_config.config || '',
|
config: proxy_config.config || '',
|
||||||
localIP: proxy_config.config && ParseProxyConfig(proxy_config.config).localIP,
|
localIP: proxy_config.config && ParseProxyConfig(proxy_config.config).localIP,
|
||||||
localPort: proxy_config.config && ParseProxyConfig(proxy_config.config).localPort,
|
localPort: proxy_config.config && ParseProxyConfig(proxy_config.config).localPort,
|
||||||
visitPreview: "",
|
visitPreview: '',
|
||||||
originalProxyConfig: proxy_config,
|
originalProxyConfig: proxy_config,
|
||||||
|
stopped: proxy_config.stopped || false,
|
||||||
} as ProxyConfigTableSchema
|
} as ProxyConfigTableSchema
|
||||||
}) ?? data,
|
}) ?? data,
|
||||||
pageCount: Math.ceil(
|
pageCount: Math.ceil(
|
||||||
@@ -122,4 +130,4 @@ export const ProxyConfigList: React.FC<ProxyConfigListProps> = ({ ProxyConfigs,
|
|||||||
|
|
||||||
function ParseProxyConfig(cfg: string): TypedProxyConfig {
|
function ParseProxyConfig(cfg: string): TypedProxyConfig {
|
||||||
return JSON.parse(cfg)
|
return JSON.parse(cfg)
|
||||||
}
|
}
|
||||||
|
27
www/components/ui/checkbox.tsx
Normal file
27
www/components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { CheckIcon } from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
const Checkbox = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
className={cn("flex items-center justify-center text-current")}
|
||||||
|
>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
||||||
|
))
|
||||||
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Checkbox }
|
@@ -8,21 +8,156 @@
|
|||||||
"repo": "VaalaCat/frp-panel"
|
"repo": "VaalaCat/frp-panel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nav": {
|
"client": {
|
||||||
"clients": "Clients",
|
"detail": {
|
||||||
"servers": "Servers",
|
"title": "Client Information",
|
||||||
"editClient": "Edit Client",
|
"version": "Version",
|
||||||
"editServer": "Edit Server",
|
"buildDate": "Build Date",
|
||||||
"trafficStats": "Traffic Stats",
|
"goVersion": "Go Version",
|
||||||
"realTimeLog": "Real-time Log",
|
"platform": "Platform",
|
||||||
"console": "Console",
|
"address": "Client Address",
|
||||||
"editTunnel": "Edit Tunnel",
|
"connectTime": "Connected Since"
|
||||||
"user": {
|
},
|
||||||
"profile": "Profile",
|
"create": {
|
||||||
"settings": "Settings"
|
"submitting": "Creating client...",
|
||||||
|
"error": "Failed to create client",
|
||||||
|
"success": "Client created successfully",
|
||||||
|
"button": "Create",
|
||||||
|
"title": "Create New Client",
|
||||||
|
"description": "Create a new client for connection. Client ID must be unique",
|
||||||
|
"id": "Client ID",
|
||||||
|
"submit": "Create"
|
||||||
|
},
|
||||||
|
"id": "ID (Click for Install Command)",
|
||||||
|
"status": "Configuration Status",
|
||||||
|
"status_configured": "Configured",
|
||||||
|
"status_unconfigured": "Not Configured",
|
||||||
|
"info": "Runtime Info/Version",
|
||||||
|
"secret": "Secret Key (Click for Start Command)",
|
||||||
|
"install": {
|
||||||
|
"title": "Installation Command",
|
||||||
|
"description": "Select your operating system and copy the installation command",
|
||||||
|
"use_github_proxy_url": "Use GitHub Proxy URL",
|
||||||
|
"windows": "Windows",
|
||||||
|
"linux": "Linux"
|
||||||
|
},
|
||||||
|
"need_upgrade": "Need Upgrade",
|
||||||
|
"temp_node": "Temporary",
|
||||||
|
"start": {
|
||||||
|
"title": "Start Command",
|
||||||
|
"description": "Copy and run the following command to start frp-panel client"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"success": "Delete successful",
|
||||||
|
"failed": "Delete failed",
|
||||||
|
"title": "Delete Client",
|
||||||
|
"description": "This action cannot be undone. Are you sure you want to permanently delete this client from our servers?",
|
||||||
|
"warning": "After deletion, running clients will not be able to connect again with existing parameters. If you need to remove client external connections, you can choose to pause the client instead.",
|
||||||
|
"confirm": "Confirm"
|
||||||
|
},
|
||||||
|
"operation": {
|
||||||
|
"stop_success": "Stop successful",
|
||||||
|
"stop_failed": "Stop failed",
|
||||||
|
"start_success": "Start successful",
|
||||||
|
"start_failed": "Start failed",
|
||||||
|
"update_failed": "Update failed",
|
||||||
|
"update_success": "Update successful"
|
||||||
|
},
|
||||||
|
"actions_menu": {
|
||||||
|
"open_menu": "Open Menu",
|
||||||
|
"title": "Client Actions",
|
||||||
|
"copy_success": "Command copied successfully. If copying fails, please click the ID or Secret Key field to copy manually",
|
||||||
|
"copy_failed": "Failed to get platform info. If copying fails, please click the ID or Secret Key field to copy manually",
|
||||||
|
"copy_start_command": "Copy Start Command",
|
||||||
|
"copy_install_command": "Copy Install Service Command",
|
||||||
|
"edit_config": "Edit Configuration",
|
||||||
|
"download_failed": "Failed to get platform info",
|
||||||
|
"download_config": "Download Configuration",
|
||||||
|
"realtime_log": "Real-time Log",
|
||||||
|
"remote_terminal": "Remote Terminal",
|
||||||
|
"pause": "Pause",
|
||||||
|
"resume": "Resume",
|
||||||
|
"delete": "Delete"
|
||||||
|
},
|
||||||
|
"join": {
|
||||||
|
"button": "Batch Configuration",
|
||||||
|
"title": "Batch Configuration",
|
||||||
|
"description": "Download the binary files in advance, run the following command, and the client will automatically generate and save the configuration file.",
|
||||||
|
"sign_token": "Generate Token"
|
||||||
|
},
|
||||||
|
"editor": {
|
||||||
|
"comment_title": "Node {{id}} Comment",
|
||||||
|
"comment_placeholder": "Comment",
|
||||||
|
"config_title": "Client {{id}} Configuration File `frpc.json` Content",
|
||||||
|
"config_description": "Only configure proxies and visitors fields, authentication and server connection information will be completed by the system",
|
||||||
|
"config_placeholder": "Configuration File Content"
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"title": "Client Statistics",
|
||||||
|
"description": "View client traffic statistics",
|
||||||
|
"label": "Client"
|
||||||
|
},
|
||||||
|
"status_online": "Online",
|
||||||
|
"status_offline": "Offline",
|
||||||
|
"status_error": "Error",
|
||||||
|
"status_pause": "Paused",
|
||||||
|
"status_unknown": "Unknown"
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"client": {
|
||||||
|
"placeholder": "Client Name"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"placeholder": "Please select...",
|
||||||
|
"notFound": "No results found",
|
||||||
|
"loading": "Loading..."
|
||||||
|
},
|
||||||
|
"proxy": {
|
||||||
|
"notFound": "No tunnel found",
|
||||||
|
"placeholder": "Tunnel name"
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"placeholder": "Server Name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"pagination": {
|
||||||
|
"rowsPerPage": "rows per page",
|
||||||
|
"page": "Page {{current}} of {{total}}",
|
||||||
|
"navigation": {
|
||||||
|
"first": "Go to first page",
|
||||||
|
"previous": "Go to previous page",
|
||||||
|
"next": "Go to next page",
|
||||||
|
"last": "Go to last page"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"placeholder": "Filter by {{column}}"
|
||||||
|
},
|
||||||
|
"noData": "No data"
|
||||||
|
},
|
||||||
|
"input": {
|
||||||
|
"keyword": {
|
||||||
|
"placeholder": "Enter keyword"
|
||||||
|
},
|
||||||
|
"search": "Search",
|
||||||
|
"list": {
|
||||||
|
"placeholder": "Please enter ...",
|
||||||
|
"add": "Add"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"traffic": {
|
"traffic": {
|
||||||
|
"chart": {
|
||||||
|
"inbound": "Inbound",
|
||||||
|
"today": "Today",
|
||||||
|
"history": "History",
|
||||||
|
"outbound": "Outbound",
|
||||||
|
"title": "Traffic Details",
|
||||||
|
"pie": {
|
||||||
|
"inbound": "Inbound",
|
||||||
|
"outbound": "Outbound"
|
||||||
|
}
|
||||||
|
},
|
||||||
"today": {
|
"today": {
|
||||||
"inbound": "Today's Inbound",
|
"inbound": "Today's Inbound",
|
||||||
"outbound": "Today's Outbound",
|
"outbound": "Today's Outbound",
|
||||||
@@ -32,93 +167,147 @@
|
|||||||
"inbound": "Historical Inbound",
|
"inbound": "Historical Inbound",
|
||||||
"outbound": "Historical Outbound",
|
"outbound": "Historical Outbound",
|
||||||
"total": "Historical Total"
|
"total": "Historical Total"
|
||||||
},
|
|
||||||
"stats": {
|
|
||||||
"title": "Traffic Statistics",
|
|
||||||
"description": "View real-time traffic statistics",
|
|
||||||
"label": "Traffic Stats"
|
|
||||||
},
|
|
||||||
"chart": {
|
|
||||||
"title": "Traffic Details",
|
|
||||||
"inbound": "Inbound",
|
|
||||||
"outbound": "Outbound",
|
|
||||||
"today": "Today",
|
|
||||||
"history": "History",
|
|
||||||
"pie": {
|
|
||||||
"inbound": "Inbound",
|
|
||||||
"outbound": "Outbound",
|
|
||||||
"total": "Total Traffic"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"login": "Login",
|
|
||||||
"register": "Register",
|
|
||||||
"logout": "Logout",
|
|
||||||
"connect": "Connect",
|
|
||||||
"disconnect": "Disconnect",
|
|
||||||
"refresh": "Refresh",
|
|
||||||
"clear": "Clear",
|
|
||||||
"clientType": "Client Type",
|
|
||||||
"streamlog": "Stream Log",
|
|
||||||
"loading": "Loading...",
|
|
||||||
"error": "Error",
|
|
||||||
"success": "Success",
|
|
||||||
"warning": "Warning",
|
|
||||||
"info": "Information",
|
|
||||||
"download": "Click here to download",
|
"download": "Click here to download",
|
||||||
"copy": "Copy",
|
"copy": "Copy",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"cancel": "Cancel",
|
|
||||||
"save": "Save",
|
|
||||||
"delete": "Delete",
|
|
||||||
"edit": "Edit",
|
|
||||||
"newWindow": "New Window"
|
|
||||||
},
|
|
||||||
"auth": {
|
|
||||||
"loginTitle": "Login",
|
|
||||||
"registerTitle": "Register",
|
|
||||||
"inputCredentials": "Enter your credentials",
|
|
||||||
"email": {
|
|
||||||
"required": "Cannot be empty",
|
|
||||||
"invalid": "Please check your email address"
|
|
||||||
},
|
|
||||||
"password": "Password",
|
|
||||||
"confirmPassword": "Confirm Password",
|
|
||||||
"usernamePlaceholder": "Username",
|
|
||||||
"emailPlaceholder": "Email address",
|
|
||||||
"passwordPlaceholder": "••••••••",
|
|
||||||
"error": "Error",
|
|
||||||
"loggingIn": "Logging in, please wait",
|
|
||||||
"loginSuccess": "Login successful, redirecting to home page",
|
|
||||||
"loginFailed": "Login failed, please try again",
|
|
||||||
"registering": "Registering, please wait",
|
|
||||||
"registerSuccess": "Registration successful, redirecting to login",
|
|
||||||
"registerFailed": "Registration failed, please try again",
|
|
||||||
"noAccount": "Don't have an account?",
|
|
||||||
"haveAccount": "Already have an account?",
|
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"register": "Register"
|
"register": "Register",
|
||||||
|
"logout": "Logout",
|
||||||
|
"clientType": "Client Type",
|
||||||
|
"disconnect": "Disconnect",
|
||||||
|
"connect": "Connect"
|
||||||
},
|
},
|
||||||
"validation": {
|
"frpc": {
|
||||||
"required": "Required field",
|
"client_plugins": {
|
||||||
"portRange": {
|
"plugin_type": "Plugin Type",
|
||||||
"min": "Port cannot be less than 1",
|
"select_plugin_type": "Please Select a Plugin",
|
||||||
"max": "Port cannot be greater than 65535"
|
"http_local_addr": "HTTP Local Address",
|
||||||
|
"http_host_header_rewrite": "HTTP Host Header Rewrite",
|
||||||
|
"http_user": "HTTP User",
|
||||||
|
"http_password": "HTTP Password"
|
||||||
},
|
},
|
||||||
"ipAddress": "Please enter a valid IP address"
|
"form": {
|
||||||
|
"title": "Edit Client",
|
||||||
|
"description": {
|
||||||
|
"warning": "Warning⚠️: The selected 'Server' must be configured in advance!",
|
||||||
|
"instruction": "Select client and server to edit configuration"
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"title": "Advanced Mode",
|
||||||
|
"description": "Edit client raw configuration file"
|
||||||
|
},
|
||||||
|
"server": "Server",
|
||||||
|
"client": "Client",
|
||||||
|
"frps_url": {
|
||||||
|
"title": "FRP server override address - Optional",
|
||||||
|
"hint": "You can override the server address stored in the master to handle the inconsistency between the domain name/IP/port and the real port during port forwarding or reverse proxy/CDN. Format: [tcp/kcp/websocket]://127.0.0.1:7000"
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"title": "Node {{id}} Comment",
|
||||||
|
"hint": "You can modify the comment in advanced mode!",
|
||||||
|
"empty": "Nothing here"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"local_addr": "Local Address",
|
||||||
|
"host_header_rewrite": "Host Header Rewrite",
|
||||||
|
"crt_path": "Certificate Path",
|
||||||
|
"key_path": "Key Path",
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"local_path": "Local Path",
|
||||||
|
"strip_prefix": "Strip Prefix",
|
||||||
|
"http_user": "HTTP User",
|
||||||
|
"http_password": "HTTP Password",
|
||||||
|
"unix_domain_socket_path": "Unix Domain Socket Path"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"platform": {
|
"proxy": {
|
||||||
"configuredServers": "Configured Servers",
|
"status": {
|
||||||
"configuredClients": "Configured Clients",
|
"create": "Create Tunnel Status",
|
||||||
"unconfiguredServers": "Unconfigured Servers",
|
"name_exists": "Name already exists",
|
||||||
"unconfiguredClients": "Unconfigured Clients",
|
"update": "Update Tunnel Status",
|
||||||
"totalServers": "Total Servers",
|
"success": "Update successful",
|
||||||
"totalClients": "Total Clients",
|
"error": "Update failed"
|
||||||
"unit": "",
|
},
|
||||||
"menuHint": "Please modify in the left menu",
|
"form": {
|
||||||
"refresh": {
|
"add": "Add Tunnel",
|
||||||
"data": "Refresh Data"
|
"name": "Name",
|
||||||
|
"protocol": "Protocol",
|
||||||
|
"type": "Type",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"config": "Tunnel Configuration",
|
||||||
|
"expand_one": "Expand",
|
||||||
|
"expand_other": "Expand",
|
||||||
|
"delete": "Delete",
|
||||||
|
"tunnel_name": "Tunnel Name",
|
||||||
|
"type_label": "Type: [{{type}}]",
|
||||||
|
"submit": "Submit Changes",
|
||||||
|
"access_method": "Access Method",
|
||||||
|
"local_port": "Local Port",
|
||||||
|
"local_ip": "Local IP",
|
||||||
|
"remote_port": "Remote Port",
|
||||||
|
"use_plugin": "Use Plugin",
|
||||||
|
"save_changes": "Save Changes",
|
||||||
|
"secret_key": "Secret Key",
|
||||||
|
"subdomain": "Subdomain",
|
||||||
|
"custom_domains": "Custom Domains",
|
||||||
|
"domain_description": "Subdomain and custom domain can be configured at the same time, but at least one of them must be not empty. If frps is configured with domain suffix, the custom domain cannot be a subdomain or wildcard domain of the domain suffix.",
|
||||||
|
"more_settings": "More Settings",
|
||||||
|
"route": "Route",
|
||||||
|
"enable_http_auth": "Enable HTTP Authentication",
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"subdomain_placeholder": "Input Subdomain"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"http": "HTTP",
|
||||||
|
"tcp": "TCP",
|
||||||
|
"udp": "UDP",
|
||||||
|
"stcp": "STCP"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"create": "Create",
|
||||||
|
"create_proxy": "Create Tunnel",
|
||||||
|
"create_proxy_description": "Expose the client's services to the server. The same client tunnel name must be unique",
|
||||||
|
"create_success": "Create successful",
|
||||||
|
"create_failed": "Create failed",
|
||||||
|
"select_server": "Select Server",
|
||||||
|
"select_client": "Select Client",
|
||||||
|
"select_proxy_type": "Select Tunnel Type",
|
||||||
|
"proxy_name": "Tunnel Name",
|
||||||
|
"invalid_config": "Invalid configuration",
|
||||||
|
"submit": "Submit"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"delete_success": "Delete Successful",
|
||||||
|
"delete_failed": "Delete Failed",
|
||||||
|
"stop_success": "Stop Successful",
|
||||||
|
"stop_failed": "Stop Failed",
|
||||||
|
"start_success": "Start Successful",
|
||||||
|
"start_failed": "Start Failed",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"delete_tunnel": "Delete Tunnel",
|
||||||
|
"delete_attention_title": "Tunnel will be permanently deleted, please confirm",
|
||||||
|
"delete_attention_description": "After deletion, Tunnel will not be recovered, please be careful",
|
||||||
|
"delete_attention_confirm": "Confirm",
|
||||||
|
"title": "Tunnel Operation"
|
||||||
|
},
|
||||||
|
"item": {
|
||||||
|
"client_id": "Client ID",
|
||||||
|
"proxy_name": "Tunnel Name",
|
||||||
|
"proxy_type": "Tunnel Type",
|
||||||
|
"server_id": "Server ID",
|
||||||
|
"status": "Status",
|
||||||
|
"visit_preview": "Visit Preview"
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"label": "Tunnel Name",
|
||||||
|
"tunnel_traffic": "Tunnel: {{name}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
@@ -138,64 +327,17 @@
|
|||||||
"title": "FRP server override address - Optional",
|
"title": "FRP server override address - Optional",
|
||||||
"description": "Clients can use this address to connect instead of the default IP. This is useful for port forwarding/CDN/reverse proxy. Format: [tcp/kcp/websocket]://127.0.0.1:7000"
|
"description": "Clients can use this address to connect instead of the default IP. This is useful for port forwarding/CDN/reverse proxy. Format: [tcp/kcp/websocket]://127.0.0.1:7000"
|
||||||
},
|
},
|
||||||
"id": "ID (Click for install command)",
|
|
||||||
"status": "Configuration Status",
|
|
||||||
"info": "Running Info/Version",
|
|
||||||
"secret": "Secret (Click for start command)",
|
|
||||||
"ip": "IP Address",
|
|
||||||
"actions": "Actions",
|
|
||||||
"status_configured": "Configured",
|
|
||||||
"status_unconfigured": "Unconfigured",
|
|
||||||
"status_online": "Online",
|
|
||||||
"status_offline": "Offline",
|
|
||||||
"status_error": "Error",
|
|
||||||
"status_unknown": "Unknown",
|
|
||||||
"status_pause": "Paused",
|
|
||||||
"install": {
|
|
||||||
"title": "Install Command",
|
|
||||||
"description": "Please select your operating system and copy the corresponding installation command",
|
|
||||||
"windows": "Windows",
|
|
||||||
"linux": "Linux",
|
|
||||||
"copy": "Copy Command"
|
|
||||||
},
|
|
||||||
"start": {
|
|
||||||
"title": "Start Command",
|
|
||||||
"description": "Copy and run the following command to start frp-panel server",
|
|
||||||
"copy": "Copy Command"
|
|
||||||
},
|
|
||||||
"actions_menu": {
|
|
||||||
"title": "Server Actions",
|
|
||||||
"edit": "Edit",
|
|
||||||
"delete": "Delete",
|
|
||||||
"start": "Start",
|
|
||||||
"stop": "Stop",
|
|
||||||
"detail": "Details",
|
|
||||||
"open_menu": "Open Menu",
|
|
||||||
"copy_command": "Copy Start Command",
|
|
||||||
"copy_success": "Copy successful, if copy failed, please click ID field to copy manually",
|
|
||||||
"copy_failed": "Failed to get platform info, if copy failed, please click ID field to copy manually",
|
|
||||||
"edit_config": "Edit Configuration",
|
|
||||||
"download_config": "Download Configuration",
|
|
||||||
"download_failed": "Failed to get platform info",
|
|
||||||
"realtime_log": "Real-time Log",
|
|
||||||
"remote_terminal": "Remote Terminal"
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"title": "Delete Server?",
|
|
||||||
"description": "This action cannot be undone. Are you sure you want to permanently delete this server from our servers?",
|
|
||||||
"warning": "After deletion, running servers will not be able to connect again with existing parameters. If you need to delete server connections, you can choose to pause the server",
|
|
||||||
"confirm": "Confirm",
|
|
||||||
"success": "Delete successful",
|
|
||||||
"failed": "Delete failed"
|
|
||||||
},
|
|
||||||
"operation": {
|
"operation": {
|
||||||
"stop_success": "Stop successful",
|
"update_failed": "Update failed",
|
||||||
"stop_failed": "Stop failed",
|
|
||||||
"start_success": "Start successful",
|
|
||||||
"start_failed": "Start failed",
|
|
||||||
"update_title": "Update Server Status",
|
|
||||||
"update_success": "Update successful",
|
"update_success": "Update successful",
|
||||||
"update_failed": "Update failed"
|
"update_title": "Update Server Status"
|
||||||
|
},
|
||||||
|
"editor": {
|
||||||
|
"comment": "Node {{id}} Comment",
|
||||||
|
"comment_placeholder": "Comment",
|
||||||
|
"config_title": "Node {{id}} Configuration File `frps.json` Content",
|
||||||
|
"config_description": "Only configure port and IP fields, authentication information will be completed by the system",
|
||||||
|
"config_placeholder": "Configuration File Content"
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"comment_title": "Node {{id}} Comment",
|
"comment_title": "Node {{id}} Comment",
|
||||||
@@ -210,214 +352,107 @@
|
|||||||
"quic_bind_port": "Quic Bind Port",
|
"quic_bind_port": "Quic Bind Port",
|
||||||
"kcp_bind_port": "KCP Bind Port"
|
"kcp_bind_port": "KCP Bind Port"
|
||||||
},
|
},
|
||||||
"editor": {
|
|
||||||
"comment": "Node {{id}} Comment",
|
|
||||||
"comment_placeholder": "Comment",
|
|
||||||
"config_title": "Node {{id}} Configuration File `frps.json` Content",
|
|
||||||
"config_description": "Only configure port and IP fields, authentication information will be completed by the system",
|
|
||||||
"config_placeholder": "Configuration File Content"
|
|
||||||
},
|
|
||||||
"create": {
|
"create": {
|
||||||
|
"submitting": "Creating server...",
|
||||||
|
"error": "Failed to create server",
|
||||||
|
"success": "Server created successfully",
|
||||||
"button": "Create",
|
"button": "Create",
|
||||||
"title": "Create New Server",
|
"title": "Create New Server",
|
||||||
"description": "Create a new server for providing service. Server ID must be unique",
|
"description": "Create a new server for providing service. Server ID must be unique",
|
||||||
"id": "Server ID",
|
"id": "Server ID",
|
||||||
"ip": "IP Address/Domain",
|
"ip": "IP Address/Domain",
|
||||||
"submit": "Create",
|
"submit": "Create"
|
||||||
"submitting": "Creating server...",
|
},
|
||||||
"success": "Server created successfully",
|
"id": "ID (Click for install command)",
|
||||||
"error": "Failed to create server"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"client": {
|
|
||||||
"id": "ID (Click for Install Command)",
|
|
||||||
"status": "Configuration Status",
|
"status": "Configuration Status",
|
||||||
"info": "Runtime Info/Version",
|
|
||||||
"secret": "Secret Key (Click for Start Command)",
|
|
||||||
"actions": "Actions",
|
|
||||||
"status_configured": "Configured",
|
"status_configured": "Configured",
|
||||||
"status_unconfigured": "Not Configured",
|
"status_unconfigured": "Unconfigured",
|
||||||
"status_online": "Online",
|
"info": "Running Info/Version",
|
||||||
"status_offline": "Offline",
|
"ip": "IP Address",
|
||||||
"status_error": "Error",
|
"secret": "Secret (Click for start command)",
|
||||||
"status_pause": "Paused",
|
|
||||||
"status_unknown": "Unknown",
|
|
||||||
"stats": {
|
|
||||||
"title": "Client Statistics",
|
|
||||||
"description": "View client traffic statistics",
|
|
||||||
"label": "Client"
|
|
||||||
},
|
|
||||||
"install": {
|
"install": {
|
||||||
"title": "Installation Command",
|
"title": "Install Command",
|
||||||
"description": "Select your operating system and copy the installation command",
|
"description": "Please select your operating system and copy the corresponding installation command",
|
||||||
"windows": "Windows",
|
"windows": "Windows",
|
||||||
"linux": "Linux",
|
"linux": "Linux"
|
||||||
"copy": "Copy Command"
|
|
||||||
},
|
|
||||||
"join": {
|
|
||||||
"button": "Batch Configuration",
|
|
||||||
"title": "Batch Configuration",
|
|
||||||
"description": "Download the binary files in advance, run the following command, and the client will automatically generate and save the configuration file.",
|
|
||||||
"copy": "Copy Command",
|
|
||||||
"sign_token": "Generate Token"
|
|
||||||
},
|
},
|
||||||
"start": {
|
"start": {
|
||||||
"title": "Start Command",
|
"title": "Start Command",
|
||||||
"description": "Copy and run the following command to start frp-panel client",
|
"description": "Copy and run the following command to start frp-panel server"
|
||||||
"copy": "Copy Command"
|
|
||||||
},
|
|
||||||
"actions_menu": {
|
|
||||||
"title": "Client Actions",
|
|
||||||
"edit": "Edit",
|
|
||||||
"delete": "Delete",
|
|
||||||
"start": "Start",
|
|
||||||
"stop": "Stop",
|
|
||||||
"detail": "Details",
|
|
||||||
"open_menu": "Open Menu",
|
|
||||||
"copy_command": "Copy Start Command",
|
|
||||||
"copy_success": "Command copied successfully. If copying fails, please click the ID field to copy manually",
|
|
||||||
"copy_failed": "Failed to get platform info. If copying fails, please click the ID field to copy manually",
|
|
||||||
"edit_config": "Edit Configuration",
|
|
||||||
"download_config": "Download Configuration",
|
|
||||||
"download_failed": "Failed to get platform info",
|
|
||||||
"realtime_log": "Real-time Log",
|
|
||||||
"remote_terminal": "Remote Terminal",
|
|
||||||
"pause": "Pause",
|
|
||||||
"resume": "Resume"
|
|
||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"title": "Delete Client",
|
|
||||||
"description": "This action cannot be undone. Are you sure you want to permanently delete this client from our servers?",
|
|
||||||
"warning": "After deletion, running clients will not be able to connect again with existing parameters. If you need to remove client external connections, you can choose to pause the client instead.",
|
|
||||||
"confirm": "Confirm",
|
|
||||||
"success": "Delete successful",
|
"success": "Delete successful",
|
||||||
"failed": "Delete failed"
|
"failed": "Delete failed",
|
||||||
|
"title": "Delete Server?",
|
||||||
|
"description": "This action cannot be undone. Are you sure you want to permanently delete this server from our servers?",
|
||||||
|
"warning": "After deletion, running servers will not be able to connect again with existing parameters. If you need to delete server connections, you can choose to pause the server",
|
||||||
|
"confirm": "Confirm"
|
||||||
},
|
},
|
||||||
"operation": {
|
"actions_menu": {
|
||||||
"stop_success": "Stop successful",
|
"open_menu": "Open Menu",
|
||||||
"stop_failed": "Stop failed",
|
"title": "Server Actions",
|
||||||
"start_success": "Start successful",
|
"copy_success": "Copy successful, if copy failed, please click ID field to copy manually",
|
||||||
"start_failed": "Start failed",
|
"copy_failed": "Failed to get platform info, if copy failed, please click ID field to copy manually",
|
||||||
"update_success": "Update successful",
|
"copy_command": "Copy Start Command",
|
||||||
"update_failed": "Update failed"
|
"edit_config": "Edit Configuration",
|
||||||
|
"download_failed": "Failed to get platform info",
|
||||||
|
"download_config": "Download Configuration",
|
||||||
|
"realtime_log": "Real-time Log",
|
||||||
|
"remote_terminal": "Remote Terminal",
|
||||||
|
"delete": "Delete"
|
||||||
},
|
},
|
||||||
"editor": {
|
"status_online": "Online",
|
||||||
"comment_title": "Node {{id}} Comment",
|
"status_offline": "Offline",
|
||||||
"comment_placeholder": "Comment",
|
"status_error": "Error",
|
||||||
"config_title": "Client {{id}} Configuration File `frpc.json` Content",
|
"status_unknown": "Unknown",
|
||||||
"config_description": "Only configure proxies and visitors fields, authentication and server connection information will be completed by the system",
|
"status_pause": "Paused"
|
||||||
"config_placeholder": "Configuration File Content"
|
|
||||||
},
|
|
||||||
"create": {
|
|
||||||
"button": "Create",
|
|
||||||
"title": "Create New Client",
|
|
||||||
"description": "Create a new client for connection. Client ID must be unique",
|
|
||||||
"id": "Client ID",
|
|
||||||
"submit": "Create",
|
|
||||||
"submitting": "Creating client...",
|
|
||||||
"success": "Client created successfully",
|
|
||||||
"error": "Failed to create client"
|
|
||||||
},
|
|
||||||
"detail": {
|
|
||||||
"title": "Client Information",
|
|
||||||
"version": "Version",
|
|
||||||
"buildDate": "Build Date",
|
|
||||||
"goVersion": "Go Version",
|
|
||||||
"platform": "Platform",
|
|
||||||
"address": "Client Address",
|
|
||||||
"connectTime": "Connected Since"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"proxy": {
|
"auth": {
|
||||||
"stats": {
|
"loggingIn": "Logging in, please wait",
|
||||||
"label": "Tunnel Name",
|
"loginSuccess": "Login successful, redirecting to home page",
|
||||||
"tunnel_traffic": "Tunnel: {{name}}",
|
"loginFailed": "Login failed, please try again",
|
||||||
"today_traffic_title": "Today's Traffic",
|
"usernamePlaceholder": "Username",
|
||||||
"today_traffic_total": "Today's Total",
|
"password": "Password",
|
||||||
"history_traffic_title": "Historical Traffic",
|
"passwordPlaceholder": "••••••••",
|
||||||
"history_traffic_total": "Historical Total"
|
"error": "Error",
|
||||||
},
|
"registering": "Registering, please wait",
|
||||||
"form": {
|
"registerSuccess": "Registration successful, redirecting to login",
|
||||||
"add": "Add Tunnel",
|
"registerFailed": "Registration failed, please try again",
|
||||||
"name": "Name",
|
"emailPlaceholder": "Email address",
|
||||||
"protocol": "Protocol",
|
"loginTitle": "Login",
|
||||||
"type": "Type",
|
"inputCredentials": "Enter your credentials",
|
||||||
"confirm": "Confirm",
|
"noAccount": "Don't have an account?",
|
||||||
"config": "Tunnel Configuration",
|
"register": "Register",
|
||||||
"expand": "Click to expand {{count}} tunnels",
|
"registerTitle": "Register",
|
||||||
"tunnel_name": "Tunnel Name",
|
"haveAccount": "Already have an account?",
|
||||||
"delete": "Delete",
|
"login": "Login"
|
||||||
"type_label": "Type: [{{type}}]",
|
},
|
||||||
"access_method": "Access Method",
|
"platform": {
|
||||||
"local_port": "Local Port",
|
"configuredServers": "Configured Servers",
|
||||||
"remote_port": "Remote Port",
|
"unit": " ",
|
||||||
"local_ip": "Local IP",
|
"menuHint": "Please modify in the left menu",
|
||||||
"subdomain": "Subdomain",
|
"configuredClients": "Configured Clients",
|
||||||
"secret_key": "Secret Key",
|
"unconfiguredServers": "Unconfigured Servers",
|
||||||
"save": "Save",
|
"unconfiguredClients": "Unconfigured Clients",
|
||||||
"save_success": "Save successful",
|
"totalServers": "Total Servers",
|
||||||
"save_error": "Save failed",
|
"totalClients": "Total Clients"
|
||||||
"save_changes": "Save Changes",
|
},
|
||||||
"submit": "Submit Changes",
|
"refresh": {
|
||||||
"default_port": "Default Port",
|
"data": "Refresh Data",
|
||||||
"port_placeholder": "Port (1-65535)",
|
"data_zh": "刷新数据"
|
||||||
"ip_placeholder": "IP Address (eg. 127.0.0.1)",
|
},
|
||||||
"subdomain_placeholder": "Input Subdomain",
|
"team": {
|
||||||
"secret_placeholder": "Input Secret Key",
|
"title": "Teams"
|
||||||
"route": "Route",
|
},
|
||||||
"custom_domains": "Custom Domains",
|
"nav": {
|
||||||
"more_settings": "More Settings",
|
"clients": "Clients",
|
||||||
"domain_description": "Subdomain and custom domain can be configured at the same time, but at least one of them must be not empty. If frps is configured with domain suffix, the custom domain cannot be a subdomain or wildcard domain of the domain suffix.",
|
"servers": "Servers",
|
||||||
"enable_http_auth": "Enable HTTP Authentication",
|
"editTunnel": "Edit Tunnel",
|
||||||
"username": "Username",
|
"editClient": "Edit Client",
|
||||||
"password": "Password"
|
"editServer": "Edit Server",
|
||||||
},
|
"trafficStats": "Traffic Stats",
|
||||||
"config": {
|
"realTimeLog": "Real-time Log",
|
||||||
"create": "Create",
|
"console": "Console"
|
||||||
"create_proxy": "Create Tunnel",
|
|
||||||
"create_proxy_description": "Expose the client's services to the server. The same client tunnel name must be unique",
|
|
||||||
"close": "Close",
|
|
||||||
"create_success": "Create successful",
|
|
||||||
"create_failed": "Create failed",
|
|
||||||
"select_server": "Select Server",
|
|
||||||
"select_client": "Select Client",
|
|
||||||
"select_proxy_type": "Select Tunnel Type",
|
|
||||||
"proxy_name": "Tunnel Name",
|
|
||||||
"invalid_config": "Invalid configuration",
|
|
||||||
"submit": "Submit"
|
|
||||||
},
|
|
||||||
"action": {
|
|
||||||
"delete_success": "Delete Successful",
|
|
||||||
"delete_failed": "Delete Failed",
|
|
||||||
"edit": "Edit",
|
|
||||||
"delete": "Delete",
|
|
||||||
"delete_tunnel": "Delete Tunnel",
|
|
||||||
"delete_attention_title": "Tunnel will be permanently deleted, please confirm",
|
|
||||||
"delete_attention_description": "After deletion, Tunnel will not be recovered, please be careful",
|
|
||||||
"delete_attention_confirm": "Confirm",
|
|
||||||
"title": "Tunnel Operation"
|
|
||||||
},
|
|
||||||
"item": {
|
|
||||||
"client_id": "Client ID",
|
|
||||||
"proxy_name": "Tunnel Name",
|
|
||||||
"proxy_type": "Tunnel Type",
|
|
||||||
"server_id": "Server ID",
|
|
||||||
"status": "Status",
|
|
||||||
"visit_preview": "Visit Preview"
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"http": "HTTP",
|
|
||||||
"tcp": "TCP",
|
|
||||||
"udp": "UDP",
|
|
||||||
"stcp": "STCP"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"update": "Update Tunnel Status",
|
|
||||||
"success": "Update successful",
|
|
||||||
"error": "Update failed",
|
|
||||||
"create": "Create Tunnel Status",
|
|
||||||
"name_exists": "Name already exists"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"frpc_form": {
|
"frpc_form": {
|
||||||
"add": "Add Client",
|
"add": "Add Client",
|
||||||
@@ -439,85 +474,5 @@
|
|||||||
"save": "Save",
|
"save": "Save",
|
||||||
"save_success": "Save successful",
|
"save_success": "Save successful",
|
||||||
"save_error": "Save failed"
|
"save_error": "Save failed"
|
||||||
},
|
|
||||||
"frpc": {
|
|
||||||
"form": {
|
|
||||||
"title": "Edit Client",
|
|
||||||
"description": {
|
|
||||||
"warning": "Warning⚠️: The selected 'Server' must be configured in advance!",
|
|
||||||
"instruction": "Select client and server to edit configuration"
|
|
||||||
},
|
|
||||||
"advanced": {
|
|
||||||
"title": "Advanced Mode",
|
|
||||||
"description": "Edit client raw configuration file"
|
|
||||||
},
|
|
||||||
"server": "Server",
|
|
||||||
"client": "Client",
|
|
||||||
"comment": {
|
|
||||||
"title": "Node {{id}} Comment",
|
|
||||||
"hint": "You can modify the comment in advanced mode!",
|
|
||||||
"empty": "Nothing here"
|
|
||||||
},
|
|
||||||
"frps_url": {
|
|
||||||
"title": "FRP server override address - Optional",
|
|
||||||
"hint": "You can override the server address stored in the master to handle the inconsistency between the domain name/IP/port and the real port during port forwarding or reverse proxy/CDN. Format: [tcp/kcp/websocket]://127.0.0.1:7000"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"refresh": {
|
|
||||||
"data": "Refresh Data",
|
|
||||||
"data_zh": "刷新数据"
|
|
||||||
},
|
|
||||||
"team": {
|
|
||||||
"title": "Teams",
|
|
||||||
"add": "Add Team"
|
|
||||||
},
|
|
||||||
"language": {
|
|
||||||
"toggle": "Toggle Language",
|
|
||||||
"zh": "Chinese",
|
|
||||||
"en": "English"
|
|
||||||
},
|
|
||||||
"selector": {
|
|
||||||
"client": {
|
|
||||||
"placeholder": "Client Name"
|
|
||||||
},
|
|
||||||
"server": {
|
|
||||||
"placeholder": "Server Name"
|
|
||||||
},
|
|
||||||
"common": {
|
|
||||||
"placeholder": "Please select...",
|
|
||||||
"loading": "Loading...",
|
|
||||||
"notFound": "No results found"
|
|
||||||
},
|
|
||||||
"proxy": {
|
|
||||||
"notFound": "No tunnel found",
|
|
||||||
"placeholder": "Tunnel name"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"input": {
|
|
||||||
"list": {
|
|
||||||
"placeholder": "Please enter ...",
|
|
||||||
"add": "Add"
|
|
||||||
},
|
|
||||||
"search": "Search",
|
|
||||||
"keyword": {
|
|
||||||
"placeholder": "Enter keyword"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"table": {
|
|
||||||
"filter": {
|
|
||||||
"placeholder": "Filter by {{column}}"
|
|
||||||
},
|
|
||||||
"noData": "No data",
|
|
||||||
"pagination": {
|
|
||||||
"rowsPerPage": "rows per page",
|
|
||||||
"page": "Page {{current}} of {{total}}",
|
|
||||||
"navigation": {
|
|
||||||
"first": "Go to first page",
|
|
||||||
"previous": "Go to previous page",
|
|
||||||
"next": "Go to next page",
|
|
||||||
"last": "Go to last page"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
125
www/i18n/locales/en_old.json
Normal file
125
www/i18n/locales/en_old.json
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
{
|
||||||
|
"nav": {
|
||||||
|
"user": {
|
||||||
|
"profile": "Profile",
|
||||||
|
"settings": "Settings"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"traffic": {
|
||||||
|
"stats": {
|
||||||
|
"title": "Traffic Statistics",
|
||||||
|
"description": "View real-time traffic statistics",
|
||||||
|
"label": "Traffic Stats"
|
||||||
|
},
|
||||||
|
"chart": {
|
||||||
|
"pie": {
|
||||||
|
"total": "Total Traffic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"clear": "Clear",
|
||||||
|
"streamlog": "Stream Log",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"error": "Error",
|
||||||
|
"success": "Success",
|
||||||
|
"warning": "Warning",
|
||||||
|
"info": "Information",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"save": "Save",
|
||||||
|
"delete": "Delete",
|
||||||
|
"edit": "Edit",
|
||||||
|
"newWindow": "New Window"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"email": {
|
||||||
|
"required": "Cannot be empty",
|
||||||
|
"invalid": "Please check your email address"
|
||||||
|
},
|
||||||
|
"confirmPassword": "Confirm Password"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"required": "Required field",
|
||||||
|
"portRange": {
|
||||||
|
"min": "Port cannot be less than 1",
|
||||||
|
"max": "Port cannot be greater than 65535"
|
||||||
|
},
|
||||||
|
"ipAddress": "Please enter a valid IP address"
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"refresh": {
|
||||||
|
"data": "Refresh Data"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"actions": "Actions",
|
||||||
|
"install": {
|
||||||
|
"copy": "Copy Command"
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"copy": "Copy Command"
|
||||||
|
},
|
||||||
|
"actions_menu": {
|
||||||
|
"edit": "Edit",
|
||||||
|
"start": "Start",
|
||||||
|
"stop": "Stop",
|
||||||
|
"detail": "Details"
|
||||||
|
},
|
||||||
|
"operation": {
|
||||||
|
"stop_success": "Stop successful",
|
||||||
|
"stop_failed": "Stop failed",
|
||||||
|
"start_success": "Start successful",
|
||||||
|
"start_failed": "Start failed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"actions": "Actions",
|
||||||
|
"install": {
|
||||||
|
"copy": "Copy Command"
|
||||||
|
},
|
||||||
|
"join": {
|
||||||
|
"copy": "Copy Command"
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"copy": "Copy Command"
|
||||||
|
},
|
||||||
|
"actions_menu": {
|
||||||
|
"edit": "Edit",
|
||||||
|
"start": "Start",
|
||||||
|
"stop": "Stop",
|
||||||
|
"detail": "Details",
|
||||||
|
"copy_command": "Copy Start Command"
|
||||||
|
},
|
||||||
|
"upgrade": ""
|
||||||
|
},
|
||||||
|
"proxy": {
|
||||||
|
"stats": {
|
||||||
|
"today_traffic_title": "Today's Traffic",
|
||||||
|
"today_traffic_total": "Today's Total",
|
||||||
|
"history_traffic_title": "Historical Traffic",
|
||||||
|
"history_traffic_total": "Historical Total"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"expand": "Click to expand {{count}} tunnels",
|
||||||
|
"save": "Save",
|
||||||
|
"save_success": "Save successful",
|
||||||
|
"save_error": "Save failed",
|
||||||
|
"default_port": "Default Port",
|
||||||
|
"port_placeholder": "Port (1-65535)",
|
||||||
|
"ip_placeholder": "IP Address (eg. 127.0.0.1)",
|
||||||
|
"secret_placeholder": "Input Secret Key"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"close": "Close"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"team": {
|
||||||
|
"add": "Add Team"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"toggle": "Toggle Language",
|
||||||
|
"zh": "Chinese",
|
||||||
|
"en": "English"
|
||||||
|
}
|
||||||
|
}
|
@@ -8,21 +8,156 @@
|
|||||||
"repo": "VaalaCat/frp-panel"
|
"repo": "VaalaCat/frp-panel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nav": {
|
"client": {
|
||||||
"clients": "客户端",
|
"detail": {
|
||||||
"servers": "服务端",
|
"title": "客户端信息",
|
||||||
"editClient": "编辑客户端",
|
"version": "版本",
|
||||||
"editServer": "编辑服务端",
|
"buildDate": "编译时间",
|
||||||
"trafficStats": "流量统计",
|
"goVersion": "Go版本",
|
||||||
"realTimeLog": "实时日志",
|
"platform": "客户端平台",
|
||||||
"console": "控制台",
|
"address": "客户端地址",
|
||||||
"editTunnel": "编辑隧道",
|
"connectTime": "连接时间"
|
||||||
"user": {
|
},
|
||||||
"profile": "个人资料",
|
"create": {
|
||||||
"settings": "设置"
|
"submitting": "正在创建客户端...",
|
||||||
|
"error": "创建客户端失败",
|
||||||
|
"success": "创建客户端成功",
|
||||||
|
"button": "新建",
|
||||||
|
"title": "新建客户端",
|
||||||
|
"description": "创建新的客户端用于连接,客户端ID必须唯一",
|
||||||
|
"id": "客户端ID",
|
||||||
|
"submit": "创建"
|
||||||
|
},
|
||||||
|
"id": "ID (点击查看安装命令)",
|
||||||
|
"status": "配置状态",
|
||||||
|
"status_configured": "已配置",
|
||||||
|
"status_unconfigured": "未配置",
|
||||||
|
"info": "运行时信息/版本",
|
||||||
|
"secret": "密钥 (点击查看启动命令)",
|
||||||
|
"install": {
|
||||||
|
"title": "安装命令",
|
||||||
|
"description": "请选择您的操作系统并复制相应的安装命令",
|
||||||
|
"use_github_proxy_url": "使用 GitHub 代理",
|
||||||
|
"windows": "Windows",
|
||||||
|
"linux": "Linux"
|
||||||
|
},
|
||||||
|
"need_upgrade": "需要升级",
|
||||||
|
"temp_node": "临时",
|
||||||
|
"start": {
|
||||||
|
"title": "启动命令",
|
||||||
|
"description": "复制并运行以下命令来启动 frp-panel 客户端"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"success": "删除成功",
|
||||||
|
"failed": "删除失败",
|
||||||
|
"title": "确定删除该客户端?",
|
||||||
|
"description": "此操作无法撤消。您确定要永久从我们的服务器中删除该客户端?",
|
||||||
|
"warning": "删除后运行中的客户端将无法通过现有参数再次连接,如果您需要删除客户端对外的连接,可以选择暂停客户端",
|
||||||
|
"confirm": "确定"
|
||||||
|
},
|
||||||
|
"operation": {
|
||||||
|
"stop_success": "停止成功",
|
||||||
|
"stop_failed": "停止失败",
|
||||||
|
"start_success": "启动成功",
|
||||||
|
"start_failed": "启动失败",
|
||||||
|
"update_failed": "更新失败",
|
||||||
|
"update_success": "更新成功"
|
||||||
|
},
|
||||||
|
"actions_menu": {
|
||||||
|
"open_menu": "打开菜单",
|
||||||
|
"title": "客户端操作",
|
||||||
|
"copy_success": "命令复制成功,如果复制失败,请点击 ID/密钥 字段手动复制",
|
||||||
|
"copy_failed": "获取平台信息失败,如果复制不成功,请点击 ID/密钥 字段手动复制",
|
||||||
|
"copy_start_command": "复制启动命令",
|
||||||
|
"copy_install_command": "复制服务安装命令",
|
||||||
|
"edit_config": "修改配置",
|
||||||
|
"download_failed": "获取平台信息失败",
|
||||||
|
"download_config": "下载配置",
|
||||||
|
"realtime_log": "实时日志",
|
||||||
|
"remote_terminal": "远程终端",
|
||||||
|
"pause": "暂停",
|
||||||
|
"resume": "启动",
|
||||||
|
"delete": "删除"
|
||||||
|
},
|
||||||
|
"join": {
|
||||||
|
"button": "批量配置",
|
||||||
|
"title": "批量配置",
|
||||||
|
"description": "提前下载好二进制文件,运行以下命令,client 将自动生成配置文件保存",
|
||||||
|
"sign_token": "生成令牌"
|
||||||
|
},
|
||||||
|
"editor": {
|
||||||
|
"comment_title": "节点 {{id}} 的备注",
|
||||||
|
"comment_placeholder": "备注",
|
||||||
|
"config_title": "客户端 {{id}} 配置文件`frpc.json`内容",
|
||||||
|
"config_description": "只需要配置proxies和visitors字段,认证信息和服务器连接信息会由系统补全",
|
||||||
|
"config_placeholder": "配置文件内容"
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"title": "客户端统计",
|
||||||
|
"description": "查看客户端流量统计信息",
|
||||||
|
"label": "客户端"
|
||||||
|
},
|
||||||
|
"status_online": "在线",
|
||||||
|
"status_offline": "离线",
|
||||||
|
"status_error": "错误",
|
||||||
|
"status_pause": "已暂停",
|
||||||
|
"status_unknown": "未知"
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"client": {
|
||||||
|
"placeholder": "客户端名称"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"placeholder": "请选择...",
|
||||||
|
"notFound": "未找到结果",
|
||||||
|
"loading": "加载中..."
|
||||||
|
},
|
||||||
|
"proxy": {
|
||||||
|
"notFound": "未找到隧道",
|
||||||
|
"placeholder": "隧道名称"
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"placeholder": "服务端名称"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"pagination": {
|
||||||
|
"rowsPerPage": "行 每页",
|
||||||
|
"page": "第 {{current}} 页, 共 {{total}} 页",
|
||||||
|
"navigation": {
|
||||||
|
"first": "第一页",
|
||||||
|
"previous": "前一页",
|
||||||
|
"next": "下一页",
|
||||||
|
"last": "最后页"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"placeholder": "根据 {{column}} 筛选"
|
||||||
|
},
|
||||||
|
"noData": "没有数据"
|
||||||
|
},
|
||||||
|
"input": {
|
||||||
|
"keyword": {
|
||||||
|
"placeholder": "请输入关键词"
|
||||||
|
},
|
||||||
|
"search": "搜索",
|
||||||
|
"list": {
|
||||||
|
"placeholder": "请输入...",
|
||||||
|
"add": "添加"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"traffic": {
|
"traffic": {
|
||||||
|
"chart": {
|
||||||
|
"inbound": "入站",
|
||||||
|
"today": "今日",
|
||||||
|
"history": "历史",
|
||||||
|
"outbound": "出站",
|
||||||
|
"title": "流量详情",
|
||||||
|
"pie": {
|
||||||
|
"inbound": "入站",
|
||||||
|
"outbound": "出站"
|
||||||
|
}
|
||||||
|
},
|
||||||
"today": {
|
"today": {
|
||||||
"inbound": "今日入站",
|
"inbound": "今日入站",
|
||||||
"outbound": "今日出站",
|
"outbound": "今日出站",
|
||||||
@@ -32,109 +167,149 @@
|
|||||||
"inbound": "历史入站",
|
"inbound": "历史入站",
|
||||||
"outbound": "历史出站",
|
"outbound": "历史出站",
|
||||||
"total": "历史总流量"
|
"total": "历史总流量"
|
||||||
},
|
|
||||||
"stats": {
|
|
||||||
"title": "流量统计",
|
|
||||||
"description": "查看实时流量统计信息",
|
|
||||||
"label": "流量统计"
|
|
||||||
},
|
|
||||||
"chart": {
|
|
||||||
"title": "流量详情",
|
|
||||||
"inbound": "入站",
|
|
||||||
"outbound": "出站",
|
|
||||||
"today": "今日",
|
|
||||||
"history": "历史",
|
|
||||||
"pie": {
|
|
||||||
"inbound": "入站",
|
|
||||||
"outbound": "出站",
|
|
||||||
"total": "总流量"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
|
"download": "点击这里下载",
|
||||||
|
"copy": "复制",
|
||||||
|
"submit": "提交",
|
||||||
"login": "登录",
|
"login": "登录",
|
||||||
"register": "注册",
|
"register": "注册",
|
||||||
"logout": "退出登录",
|
"logout": "退出登录",
|
||||||
"connect": "连接",
|
|
||||||
"refresh": "刷新",
|
|
||||||
"disconnect": "断开连接",
|
|
||||||
"submit": "提交",
|
|
||||||
"cancel": "取消",
|
|
||||||
"save": "保存",
|
|
||||||
"delete": "删除",
|
|
||||||
"edit": "编辑",
|
|
||||||
"newWindow": "新窗口",
|
|
||||||
"clientType": "客户端类型",
|
"clientType": "客户端类型",
|
||||||
"loading": "加载中...",
|
"disconnect": "断开连接",
|
||||||
"error": "错误",
|
"connect": "连接"
|
||||||
"success": "成功",
|
|
||||||
"warning": "警告",
|
|
||||||
"info": "信息",
|
|
||||||
"download": "点击这里下载",
|
|
||||||
"copy": "复制",
|
|
||||||
"clear": "清空",
|
|
||||||
"streamlog": "实时日志"
|
|
||||||
},
|
},
|
||||||
"auth": {
|
"frpc": {
|
||||||
"loginTitle": "登录",
|
"client_plugins": {
|
||||||
"registerTitle": "注册",
|
"plugin_type": "插件类型",
|
||||||
"inputCredentials": "请输入您的账号信息",
|
"select_plugin_type": "选择插件类型",
|
||||||
"email": {
|
"http_local_addr": "HTTP 本地地址",
|
||||||
"required": "不能为空",
|
"http_host_header_rewrite": "HTTP 主机头重写",
|
||||||
"invalid": "请检查邮箱地址格式"
|
"http_user": "HTTP 用户名",
|
||||||
|
"http_password": "HTTP 密码"
|
||||||
},
|
},
|
||||||
"password": "密码",
|
"form": {
|
||||||
"confirmPassword": "确认密码",
|
"title": "编辑客户端",
|
||||||
"usernamePlaceholder": "用户名",
|
"description": {
|
||||||
"emailPlaceholder": "邮箱地址",
|
"warning": "注意⚠️:选择的「服务端」必须提前配置!",
|
||||||
"passwordPlaceholder": "••••••••",
|
"instruction": "选择客户端和服务端以配置"
|
||||||
"error": "错误",
|
},
|
||||||
"loggingIn": "登录中,请稍候",
|
"advanced": {
|
||||||
"loginSuccess": "登录成功,正在跳转到首页",
|
"title": "高级模式",
|
||||||
"loginFailed": "登录失败,请重试",
|
"description": "编辑客户端原始配置文件"
|
||||||
"registering": "注册中,请稍候",
|
},
|
||||||
"registerSuccess": "注册成功,正在跳转到登录页",
|
"server": "服务端",
|
||||||
"registerFailed": "注册失败,请重试",
|
"client": "客户端",
|
||||||
"noAccount": "还没有账号?",
|
"frps_url": {
|
||||||
"haveAccount": "已有账号?",
|
"title": "FRP 服务端覆盖地址 - 可选",
|
||||||
"login": "登录",
|
"hint": "可以覆盖 master 存储的服务端地址,用于处理端口转发或反向代理/CDN时,域名/IP/端口和真实端口不一致的问题。格式:[tcp/kcp/websocket]://127.0.0.1:7000"
|
||||||
"register": "注册"
|
},
|
||||||
},
|
"comment": {
|
||||||
"validation": {
|
"title": "节点 {{id}} 的备注",
|
||||||
"required": "不能为空",
|
"hint": "可以到高级模式修改备注哦!",
|
||||||
"portRange": {
|
"empty": "空空如也"
|
||||||
"min": "端口号不能小于 1",
|
}
|
||||||
"max": "端口号不能大于 65535"
|
|
||||||
},
|
},
|
||||||
"ipAddress": "请输入正确的IP地址"
|
"plugins": {
|
||||||
|
"local_addr": "本地地址",
|
||||||
|
"host_header_rewrite": "主机头重写",
|
||||||
|
"crt_path": "证书路径",
|
||||||
|
"key_path": "密钥路径",
|
||||||
|
"username": "用户名",
|
||||||
|
"password": "密码",
|
||||||
|
"local_path": "本地路径",
|
||||||
|
"strip_prefix": "前缀去除",
|
||||||
|
"http_user": "HTTP 用户名",
|
||||||
|
"http_password": "HTTP 密码",
|
||||||
|
"unix_domain_socket_path": "Unix 域套接字路径"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"platform": {
|
"proxy": {
|
||||||
"configuredServers": "已配置服务端数",
|
"status": {
|
||||||
"configuredClients": "已配置客户端数",
|
"create": "创建隧道状态",
|
||||||
"unconfiguredServers": "未配置服务端数",
|
"name_exists": "名称重复",
|
||||||
"unconfiguredClients": "未配置客户端数",
|
"update": "更新隧道状态",
|
||||||
"totalServers": "服务端总数",
|
"success": "更新成功",
|
||||||
"totalClients": "客户端总数",
|
"error": "更新失败"
|
||||||
"unit": "个",
|
},
|
||||||
"menuHint": "请前往左侧🫲菜单修改",
|
"form": {
|
||||||
"refresh": {
|
"add": "新增隧道",
|
||||||
"data": "刷新数据"
|
"name": "名称",
|
||||||
|
"protocol": "协议",
|
||||||
|
"type": "类型",
|
||||||
|
"confirm": "确定",
|
||||||
|
"config": "隧道配置",
|
||||||
|
"expand_other": "展开其他",
|
||||||
|
"delete": "删除",
|
||||||
|
"tunnel_name": "隧道名称",
|
||||||
|
"type_label": "类型:「{{type}}」",
|
||||||
|
"submit": "提交变更",
|
||||||
|
"access_method": "访问方式",
|
||||||
|
"local_port": "本地端口",
|
||||||
|
"local_ip": "本地IP",
|
||||||
|
"remote_port": "远程端口",
|
||||||
|
"use_plugin": "使用插件",
|
||||||
|
"save_changes": "暂存修改",
|
||||||
|
"secret_key": "密钥",
|
||||||
|
"subdomain": "子域名",
|
||||||
|
"custom_domains": "自定义域名",
|
||||||
|
"domain_description": "「子域名」和「自定义域名」可以同时配置,但二者至少有一个不为空。如果 frps 配置了「域名后缀」,则「自定义域名」中不能是属于「域名后缀」的「子域名」或者「泛域名」",
|
||||||
|
"more_settings": "更多设置",
|
||||||
|
"route": "路由",
|
||||||
|
"enable_http_auth": "启用 HTTP 认证",
|
||||||
|
"username": "用户名",
|
||||||
|
"password": "密码",
|
||||||
|
"subdomain_placeholder": "请输入子域名"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"http": "HTTP",
|
||||||
|
"tcp": "TCP",
|
||||||
|
"udp": "UDP",
|
||||||
|
"stcp": "STCP"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"create": "创建",
|
||||||
|
"create_proxy": "创建隧道",
|
||||||
|
"create_proxy_description": "将客户端的服务暴露在服务端,同一个客户端隧道名称必须唯一",
|
||||||
|
"create_success": "创建成功",
|
||||||
|
"create_failed": "创建失败",
|
||||||
|
"select_server": "选择服务器",
|
||||||
|
"select_client": "选择客户端",
|
||||||
|
"select_proxy_type": "选择隧道类型",
|
||||||
|
"proxy_name": "隧道名称",
|
||||||
|
"invalid_config": "配置不正确",
|
||||||
|
"submit": "提交"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"delete_success": "删除成功",
|
||||||
|
"delete_failed": "删除失败",
|
||||||
|
"stop_success": "停止成功",
|
||||||
|
"stop_failed": "停止失败",
|
||||||
|
"start_success": "启动成功",
|
||||||
|
"start_failed": "启动失败",
|
||||||
|
"edit": "编辑",
|
||||||
|
"delete": "删除",
|
||||||
|
"delete_tunnel": "删除隧道",
|
||||||
|
"delete_attention_title": "隧道将被永久删除,请确认",
|
||||||
|
"delete_attention_description": "删除后将无法恢复,请谨慎操作",
|
||||||
|
"delete_attention_confirm": "确认删除",
|
||||||
|
"title": "隧道操作"
|
||||||
|
},
|
||||||
|
"item": {
|
||||||
|
"client_id": "客户端ID",
|
||||||
|
"proxy_name": "隧道名称",
|
||||||
|
"proxy_type": "隧道类型",
|
||||||
|
"server_id": "服务端ID",
|
||||||
|
"status": "状态",
|
||||||
|
"visit_preview": "访问预览"
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"label": "隧道名称",
|
||||||
|
"tunnel_traffic": "隧道:{{name}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"id": "ID (点击查看安装命令)",
|
|
||||||
"status": "配置状态",
|
|
||||||
"info": "运行信息/版本",
|
|
||||||
"secret": "密钥 (点击查看启动命令)",
|
|
||||||
"ip": "IP地址",
|
|
||||||
"actions": "操作",
|
|
||||||
"status_configured": "已配置",
|
|
||||||
"status_unconfigured": "未配置",
|
|
||||||
"status_online": "在线",
|
|
||||||
"status_offline": "离线",
|
|
||||||
"status_error": "错误",
|
|
||||||
"status_unknown": "未知",
|
|
||||||
"status_pause": "已暂停",
|
|
||||||
"configuration": "服务器配置",
|
"configuration": "服务器配置",
|
||||||
"warning": {
|
"warning": {
|
||||||
"title": "警告⚠️:修改配置文件后服务器将退出",
|
"title": "警告⚠️:修改配置文件后服务器将退出",
|
||||||
@@ -151,51 +326,10 @@
|
|||||||
"title": "FRP 服务器覆盖地址 - 可选",
|
"title": "FRP 服务器覆盖地址 - 可选",
|
||||||
"description": "客户端可以使用该地址连接,而不是使用默认的IP。在端口转发/CDN/反向代理时很有用。格式:[tcp/kcp/websocket]://127.0.0.1:7000"
|
"description": "客户端可以使用该地址连接,而不是使用默认的IP。在端口转发/CDN/反向代理时很有用。格式:[tcp/kcp/websocket]://127.0.0.1:7000"
|
||||||
},
|
},
|
||||||
"install": {
|
|
||||||
"title": "安装命令",
|
|
||||||
"description": "请选择您的操作系统并复制相应的安装命令",
|
|
||||||
"windows": "Windows",
|
|
||||||
"linux": "Linux",
|
|
||||||
"copy": "复制命令"
|
|
||||||
},
|
|
||||||
"start": {
|
|
||||||
"title": "启动命令",
|
|
||||||
"description": "复制并运行以下命令来启动 frp-panel 服务端",
|
|
||||||
"copy": "复制命令"
|
|
||||||
},
|
|
||||||
"actions_menu": {
|
|
||||||
"title": "服务器操作",
|
|
||||||
"edit": "编辑",
|
|
||||||
"delete": "删除",
|
|
||||||
"start": "启动",
|
|
||||||
"stop": "停止",
|
|
||||||
"detail": "详情",
|
|
||||||
"open_menu": "打开菜单",
|
|
||||||
"copy_command": "复制启动命令",
|
|
||||||
"copy_success": "复制成功,如果复制不成功,请点击ID字段手动复制",
|
|
||||||
"copy_failed": "获取平台信息失败,如果复制不成功,请点击ID字段手动复制",
|
|
||||||
"edit_config": "修改配置",
|
|
||||||
"download_config": "下载配置",
|
|
||||||
"download_failed": "获取平台信息失败",
|
|
||||||
"realtime_log": "实时日志",
|
|
||||||
"remote_terminal": "远程终端"
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"title": "确定删除该服务器?",
|
|
||||||
"description": "此操作无法撤消。您确定要永久从我们的服务器中删除该服务器?",
|
|
||||||
"warning": "删除后运行中的服务器将无法通过现有参数再次连接,如果您需要删除服务器对外的连接,可以选择暂停服务器",
|
|
||||||
"confirm": "确定",
|
|
||||||
"success": "删除成功",
|
|
||||||
"failed": "删除失败"
|
|
||||||
},
|
|
||||||
"operation": {
|
"operation": {
|
||||||
"stop_success": "停止成功",
|
"update_failed": "修改失败",
|
||||||
"stop_failed": "停止失败",
|
|
||||||
"start_success": "启动成功",
|
|
||||||
"start_failed": "启动失败",
|
|
||||||
"update_title": "修改服务端状态",
|
|
||||||
"update_success": "修改成功",
|
"update_success": "修改成功",
|
||||||
"update_failed": "修改失败"
|
"update_title": "修改服务端状态"
|
||||||
},
|
},
|
||||||
"editor": {
|
"editor": {
|
||||||
"comment": "节点 {{id}} 的备注",
|
"comment": "节点 {{id}} 的备注",
|
||||||
@@ -218,206 +352,105 @@
|
|||||||
"kcp_bind_port": "KCP 监听端口"
|
"kcp_bind_port": "KCP 监听端口"
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
|
"submitting": "正在创建服务端...",
|
||||||
|
"error": "创建服务端失败",
|
||||||
|
"success": "创建服务端成功",
|
||||||
"button": "新建",
|
"button": "新建",
|
||||||
"title": "新建服务端",
|
"title": "新建服务端",
|
||||||
"description": "创建新的服务端用于提供服务,服务端ID必须唯一",
|
"description": "创建新的服务端用于提供服务,服务端ID必须唯一",
|
||||||
"id": "服务端ID",
|
"id": "服务端ID",
|
||||||
"ip": "IP地址/域名",
|
"ip": "IP地址/域名",
|
||||||
"submit": "创建",
|
"submit": "创建"
|
||||||
"submitting": "正在创建服务端...",
|
},
|
||||||
"success": "创建服务端成功",
|
|
||||||
"error": "创建服务端失败"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"client": {
|
|
||||||
"id": "ID (点击查看安装命令)",
|
"id": "ID (点击查看安装命令)",
|
||||||
"status": "配置状态",
|
"status": "配置状态",
|
||||||
"info": "运行时信息/版本",
|
|
||||||
"secret": "密钥 (点击查看启动命令)",
|
|
||||||
"actions": "操作",
|
|
||||||
"status_configured": "已配置",
|
"status_configured": "已配置",
|
||||||
"status_unconfigured": "未配置",
|
"status_unconfigured": "未配置",
|
||||||
"status_online": "在线",
|
"info": "运行信息/版本",
|
||||||
"status_offline": "离线",
|
"ip": "IP地址",
|
||||||
"status_error": "错误",
|
"secret": "密钥 (点击查看启动命令)",
|
||||||
"status_pause": "已暂停",
|
|
||||||
"status_unknown": "未知",
|
|
||||||
"stats": {
|
|
||||||
"title": "客户端统计",
|
|
||||||
"description": "查看客户端流量统计信息",
|
|
||||||
"label": "客户端"
|
|
||||||
},
|
|
||||||
"install": {
|
"install": {
|
||||||
"title": "安装命令",
|
"title": "安装命令",
|
||||||
"description": "请选择您的操作系统并复制相应的安装命令",
|
"description": "请选择您的操作系统并复制相应的安装命令",
|
||||||
"windows": "Windows",
|
"windows": "Windows",
|
||||||
"linux": "Linux",
|
"linux": "Linux"
|
||||||
"copy": "复制命令"
|
|
||||||
},
|
},
|
||||||
"start": {
|
"start": {
|
||||||
"title": "启动命令",
|
"title": "启动命令",
|
||||||
"description": "复制并运行以下命令来启动 frp-panel 客户端",
|
"description": "复制并运行以下命令来启动 frp-panel 服务端"
|
||||||
"copy": "复制命令"
|
|
||||||
},
|
|
||||||
"join": {
|
|
||||||
"button": "批量配置",
|
|
||||||
"title": "批量配置",
|
|
||||||
"description": "提前下载好二进制文件,运行以下命令,client 将自动生成配置文件保存",
|
|
||||||
"copy": "复制命令",
|
|
||||||
"sign_token": "生成令牌"
|
|
||||||
},
|
|
||||||
"actions_menu": {
|
|
||||||
"title": "客户端操作",
|
|
||||||
"edit": "编辑",
|
|
||||||
"delete": "删除",
|
|
||||||
"start": "启动",
|
|
||||||
"stop": "停止",
|
|
||||||
"detail": "详情",
|
|
||||||
"open_menu": "打开菜单",
|
|
||||||
"copy_command": "复制启动命令",
|
|
||||||
"copy_success": "命令复制成功,如果复制失败,请点击ID字段手动复制",
|
|
||||||
"copy_failed": "获取平台信息失败,如果复制不成功,请点击ID字段手动复制",
|
|
||||||
"edit_config": "修改配置",
|
|
||||||
"download_config": "下载配置",
|
|
||||||
"download_failed": "获取平台信息失败",
|
|
||||||
"realtime_log": "实时日志",
|
|
||||||
"remote_terminal": "远程终端",
|
|
||||||
"pause": "暂停",
|
|
||||||
"resume": "启动"
|
|
||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"title": "确定删除该客户端?",
|
|
||||||
"description": "此操作无法撤消。您确定要永久从我们的服务器中删除该客户端?",
|
|
||||||
"warning": "删除后运行中的客户端将无法通过现有参数再次连接,如果您需要删除客户端对外的连接,可以选择暂停客户端",
|
|
||||||
"confirm": "确定",
|
|
||||||
"success": "删除成功",
|
"success": "删除成功",
|
||||||
"failed": "删除失败"
|
"failed": "删除失败",
|
||||||
|
"title": "确定删除该服务器?",
|
||||||
|
"description": "此操作无法撤消。您确定要永久从我们的服务器中删除该服务器?",
|
||||||
|
"warning": "删除后运行中的服务器将无法通过现有参数再次连接,如果您需要删除服务器对外的连接,可以选择暂停服务器",
|
||||||
|
"confirm": "确定"
|
||||||
},
|
},
|
||||||
"operation": {
|
"actions_menu": {
|
||||||
"stop_success": "停止成功",
|
"open_menu": "打开菜单",
|
||||||
"stop_failed": "停止失败",
|
"title": "服务器操作",
|
||||||
"start_success": "启动成功",
|
"copy_success": "复制成功,如果复制不成功,请点击ID字段手动复制",
|
||||||
"start_failed": "启动失败",
|
"copy_failed": "获取平台信息失败,如果复制不成功,请点击ID字段手动复制",
|
||||||
"update_success": "更新成功",
|
"copy_command": "复制启动命令",
|
||||||
"update_failed": "更新失败"
|
"edit_config": "修改配置",
|
||||||
|
"download_failed": "获取平台信息失败",
|
||||||
|
"download_config": "下载配置",
|
||||||
|
"realtime_log": "实时日志",
|
||||||
|
"remote_terminal": "远程终端",
|
||||||
|
"delete": "删除"
|
||||||
},
|
},
|
||||||
"editor": {
|
"status_online": "在线",
|
||||||
"comment_title": "节点 {{id}} 的备注",
|
"status_offline": "离线",
|
||||||
"comment_placeholder": "备注",
|
"status_error": "错误",
|
||||||
"config_title": "客户端 {{id}} 配置文件`frpc.json`内容",
|
"status_unknown": "未知",
|
||||||
"config_description": "只需要配置proxies和visitors字段,认证信息和服务器连接信息会由系统补全",
|
"status_pause": "已暂停"
|
||||||
"config_placeholder": "配置文件内容"
|
|
||||||
},
|
|
||||||
"create": {
|
|
||||||
"button": "新建",
|
|
||||||
"title": "新建客户端",
|
|
||||||
"description": "创建新的客户端用于连接,客户端ID必须唯一",
|
|
||||||
"id": "客户端ID",
|
|
||||||
"submit": "创建",
|
|
||||||
"submitting": "正在创建客户端...",
|
|
||||||
"success": "创建客户端成功",
|
|
||||||
"error": "创建客户端失败"
|
|
||||||
},
|
|
||||||
"detail": {
|
|
||||||
"title": "客户端信息",
|
|
||||||
"version": "版本",
|
|
||||||
"buildDate": "编译时间",
|
|
||||||
"goVersion": "Go版本",
|
|
||||||
"platform": "客户端平台",
|
|
||||||
"address": "客户端地址",
|
|
||||||
"connectTime": "连接时间"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"proxy": {
|
"auth": {
|
||||||
"stats": {
|
"loggingIn": "登录中,请稍候",
|
||||||
"label": "隧道名称",
|
"loginSuccess": "登录成功,正在跳转到首页",
|
||||||
"tunnel_traffic": "隧道:{{name}}",
|
"loginFailed": "登录失败,请重试",
|
||||||
"today_traffic_title": "今日流量",
|
"usernamePlaceholder": "用户名",
|
||||||
"today_traffic_total": "今日总计",
|
"password": "密码",
|
||||||
"history_traffic_title": "历史流量",
|
"passwordPlaceholder": "••••••••",
|
||||||
"history_traffic_total": "历史总计"
|
"error": "错误",
|
||||||
},
|
"registering": "注册中,请稍候",
|
||||||
"form": {
|
"registerSuccess": "注册成功,正在跳转到登录页",
|
||||||
"add": "新增隧道",
|
"registerFailed": "注册失败,请重试",
|
||||||
"name": "名称",
|
"emailPlaceholder": "邮箱地址",
|
||||||
"protocol": "协议",
|
"loginTitle": "登录",
|
||||||
"type": "类型",
|
"inputCredentials": "请输入您的账号信息",
|
||||||
"confirm": "确定",
|
"noAccount": "还没有账号?",
|
||||||
"config": "隧道配置",
|
"register": "注册",
|
||||||
"expand": "点击展开{{count}}条隧道",
|
"registerTitle": "注册",
|
||||||
"tunnel_name": "隧道名称",
|
"haveAccount": "已有账号?",
|
||||||
"delete": "删除",
|
"login": "登录"
|
||||||
"type_label": "类型:「{{type}}」",
|
},
|
||||||
"access_method": "访问方式",
|
"platform": {
|
||||||
"local_port": "本地端口",
|
"configuredServers": "已配置服务端数",
|
||||||
"remote_port": "远程端口",
|
"unit": "个",
|
||||||
"local_ip": "本地IP",
|
"menuHint": "请前往左侧🫲菜单修改",
|
||||||
"subdomain": "子域名",
|
"configuredClients": "已配置客户端数",
|
||||||
"secret_key": "密钥",
|
"unconfiguredServers": "未配置服务端数",
|
||||||
"save": "保存",
|
"unconfiguredClients": "未配置客户端数",
|
||||||
"save_success": "保存成功",
|
"totalServers": "服务端总数",
|
||||||
"save_error": "保存失败",
|
"totalClients": "客户端总数"
|
||||||
"save_changes": "暂存修改",
|
},
|
||||||
"submit": "提交变更",
|
"refresh": {
|
||||||
"default_port": "默认端口",
|
"data": "刷新数据"
|
||||||
"port_placeholder": "请输入端口号 (1-65535)",
|
},
|
||||||
"ip_placeholder": "请输入IP地址 (例如: 127.0.0.1)",
|
"team": {
|
||||||
"subdomain_placeholder": "请输入子域名",
|
"title": "租户"
|
||||||
"secret_placeholder": "请输入密钥",
|
},
|
||||||
"route": "路由",
|
"nav": {
|
||||||
"custom_domains": "自定义域名",
|
"clients": "客户端",
|
||||||
"more_settings": "更多设置",
|
"servers": "服务端",
|
||||||
"domain_description": "「子域名」和「自定义域名」可以同时配置,但二者至少有一个不为空。如果 frps 配置了「域名后缀」,则「自定义域名」中不能是属于「域名后缀」的「子域名」或者「泛域名」",
|
"editTunnel": "编辑隧道",
|
||||||
"enable_http_auth": "启用 HTTP 认证",
|
"editClient": "编辑客户端",
|
||||||
"username": "用户名",
|
"editServer": "编辑服务端",
|
||||||
"password": "密码"
|
"trafficStats": "流量统计",
|
||||||
},
|
"realTimeLog": "实时日志",
|
||||||
"config": {
|
"console": "控制台"
|
||||||
"create": "创建",
|
|
||||||
"create_proxy": "创建隧道",
|
|
||||||
"create_proxy_description": "将客户端的服务暴露在服务端,同一个客户端隧道名称必须唯一",
|
|
||||||
"close": "关闭",
|
|
||||||
"create_success": "创建成功",
|
|
||||||
"create_failed": "创建失败",
|
|
||||||
"select_server": "选择服务器",
|
|
||||||
"select_client": "选择客户端",
|
|
||||||
"select_proxy_type": "选择隧道类型",
|
|
||||||
"proxy_name": "隧道名称",
|
|
||||||
"invalid_config": "配置不正确",
|
|
||||||
"submit": "提交"
|
|
||||||
},
|
|
||||||
"action": {
|
|
||||||
"delete_success": "删除成功",
|
|
||||||
"delete_failed": "删除失败",
|
|
||||||
"edit": "编辑",
|
|
||||||
"delete": "删除",
|
|
||||||
"delete_tunnel": "删除隧道",
|
|
||||||
"delete_attention_title": "隧道将被永久删除,请确认",
|
|
||||||
"delete_attention_description": "删除后将无法恢复,请谨慎操作",
|
|
||||||
"delete_attention_confirm": "确认删除",
|
|
||||||
"title": "隧道操作"
|
|
||||||
},
|
|
||||||
"item": {
|
|
||||||
"client_id": "客户端ID",
|
|
||||||
"proxy_name": "隧道名称",
|
|
||||||
"proxy_type": "隧道类型",
|
|
||||||
"server_id": "服务端ID",
|
|
||||||
"status": "状态",
|
|
||||||
"visit_preview": "访问预览"
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"http": "HTTP",
|
|
||||||
"tcp": "TCP",
|
|
||||||
"udp": "UDP",
|
|
||||||
"stcp": "STCP"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"update": "更新隧道状态",
|
|
||||||
"success": "更新成功",
|
|
||||||
"error": "更新失败",
|
|
||||||
"create": "创建隧道状态",
|
|
||||||
"name_exists": "名称重复"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"frpc_form": {
|
"frpc_form": {
|
||||||
"add": "新增隧道",
|
"add": "新增隧道",
|
||||||
@@ -439,84 +472,5 @@
|
|||||||
"save": "保存",
|
"save": "保存",
|
||||||
"save_success": "保存成功",
|
"save_success": "保存成功",
|
||||||
"save_error": "保存失败"
|
"save_error": "保存失败"
|
||||||
},
|
|
||||||
"frpc": {
|
|
||||||
"form": {
|
|
||||||
"title": "编辑客户端",
|
|
||||||
"description": {
|
|
||||||
"warning": "注意⚠️:选择的「服务端」必须提前配置!",
|
|
||||||
"instruction": "选择客户端和服务端以配置"
|
|
||||||
},
|
|
||||||
"advanced": {
|
|
||||||
"title": "高级模式",
|
|
||||||
"description": "编辑客户端原始配置文件"
|
|
||||||
},
|
|
||||||
"server": "服务端",
|
|
||||||
"client": "客户端",
|
|
||||||
"comment": {
|
|
||||||
"title": "节点 {{id}} 的备注",
|
|
||||||
"hint": "可以到高级模式修改备注哦!",
|
|
||||||
"empty": "空空如也"
|
|
||||||
},
|
|
||||||
"frps_url": {
|
|
||||||
"title": "FRP 服务端覆盖地址 - 可选",
|
|
||||||
"hint": "可以覆盖 master 存储的服务端地址,用于处理端口转发或反向代理/CDN时,域名/IP/端口和真实端口不一致的问题。格式:[tcp/kcp/websocket]://127.0.0.1:7000"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"refresh": {
|
|
||||||
"data": "刷新数据"
|
|
||||||
},
|
|
||||||
"team": {
|
|
||||||
"title": "租户",
|
|
||||||
"add": "添加租户"
|
|
||||||
},
|
|
||||||
"language": {
|
|
||||||
"toggle": "切换语言",
|
|
||||||
"zh": "中文",
|
|
||||||
"en": "English"
|
|
||||||
},
|
|
||||||
"selector": {
|
|
||||||
"client": {
|
|
||||||
"placeholder": "客户端名称"
|
|
||||||
},
|
|
||||||
"server": {
|
|
||||||
"placeholder": "服务端名称"
|
|
||||||
},
|
|
||||||
"common": {
|
|
||||||
"placeholder": "请选择...",
|
|
||||||
"loading": "加载中...",
|
|
||||||
"notFound": "未找到结果"
|
|
||||||
},
|
|
||||||
"proxy": {
|
|
||||||
"notFound": "未找到隧道",
|
|
||||||
"placeholder": "隧道名称"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"input": {
|
|
||||||
"list": {
|
|
||||||
"placeholder": "请输入...",
|
|
||||||
"add": "添加"
|
|
||||||
},
|
|
||||||
"search": "搜索",
|
|
||||||
"keyword": {
|
|
||||||
"placeholder": "请输入关键词"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"table": {
|
|
||||||
"filter": {
|
|
||||||
"placeholder": "根据 {{column}} 筛选"
|
|
||||||
},
|
|
||||||
"noData": "没有数据",
|
|
||||||
"pagination": {
|
|
||||||
"rowsPerPage": "行 每页",
|
|
||||||
"page": "第 {{current}} 页, 共 {{total}} 页",
|
|
||||||
"navigation": {
|
|
||||||
"first": "第一页",
|
|
||||||
"previous": "前一页",
|
|
||||||
"next": "下一页",
|
|
||||||
"last": "最后页"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
125
www/i18n/locales/zh_old.json
Normal file
125
www/i18n/locales/zh_old.json
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
{
|
||||||
|
"nav": {
|
||||||
|
"user": {
|
||||||
|
"profile": "个人资料",
|
||||||
|
"settings": "设置"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"traffic": {
|
||||||
|
"stats": {
|
||||||
|
"title": "流量统计",
|
||||||
|
"description": "查看实时流量统计信息",
|
||||||
|
"label": "流量统计"
|
||||||
|
},
|
||||||
|
"chart": {
|
||||||
|
"pie": {
|
||||||
|
"total": "总流量"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"refresh": "刷新",
|
||||||
|
"cancel": "取消",
|
||||||
|
"save": "保存",
|
||||||
|
"delete": "删除",
|
||||||
|
"edit": "编辑",
|
||||||
|
"newWindow": "新窗口",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"error": "错误",
|
||||||
|
"success": "成功",
|
||||||
|
"warning": "警告",
|
||||||
|
"info": "信息",
|
||||||
|
"clear": "清空",
|
||||||
|
"streamlog": "实时日志"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"email": {
|
||||||
|
"required": "不能为空",
|
||||||
|
"invalid": "请检查邮箱地址格式"
|
||||||
|
},
|
||||||
|
"confirmPassword": "确认密码"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"required": "不能为空",
|
||||||
|
"portRange": {
|
||||||
|
"min": "端口号不能小于 1",
|
||||||
|
"max": "端口号不能大于 65535"
|
||||||
|
},
|
||||||
|
"ipAddress": "请输入正确的IP地址"
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"refresh": {
|
||||||
|
"data": "刷新数据"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"actions": "操作",
|
||||||
|
"install": {
|
||||||
|
"copy": "复制命令"
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"copy": "复制命令"
|
||||||
|
},
|
||||||
|
"actions_menu": {
|
||||||
|
"edit": "编辑",
|
||||||
|
"start": "启动",
|
||||||
|
"stop": "停止",
|
||||||
|
"detail": "详情"
|
||||||
|
},
|
||||||
|
"operation": {
|
||||||
|
"stop_success": "停止成功",
|
||||||
|
"stop_failed": "停止失败",
|
||||||
|
"start_success": "启动成功",
|
||||||
|
"start_failed": "启动失败"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"actions": "操作",
|
||||||
|
"install": {
|
||||||
|
"copy": "复制命令"
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"copy": "复制命令"
|
||||||
|
},
|
||||||
|
"join": {
|
||||||
|
"copy": "复制命令"
|
||||||
|
},
|
||||||
|
"actions_menu": {
|
||||||
|
"edit": "编辑",
|
||||||
|
"start": "启动",
|
||||||
|
"stop": "停止",
|
||||||
|
"detail": "详情",
|
||||||
|
"copy_command": "复制启动命令"
|
||||||
|
},
|
||||||
|
"upgrade": ""
|
||||||
|
},
|
||||||
|
"proxy": {
|
||||||
|
"stats": {
|
||||||
|
"today_traffic_title": "今日流量",
|
||||||
|
"today_traffic_total": "今日总计",
|
||||||
|
"history_traffic_title": "历史流量",
|
||||||
|
"history_traffic_total": "历史总计"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"expand": "点击展开{{count}}条隧道",
|
||||||
|
"save": "保存",
|
||||||
|
"save_success": "保存成功",
|
||||||
|
"save_error": "保存失败",
|
||||||
|
"default_port": "默认端口",
|
||||||
|
"port_placeholder": "请输入端口号 (1-65535)",
|
||||||
|
"ip_placeholder": "请输入IP地址 (例如: 127.0.0.1)",
|
||||||
|
"secret_placeholder": "请输入密钥"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"close": "关闭"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"team": {
|
||||||
|
"add": "添加租户"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"toggle": "切换语言",
|
||||||
|
"zh": "中文",
|
||||||
|
"en": "English"
|
||||||
|
}
|
||||||
|
}
|
10
www/i18next-parser.config.js
Normal file
10
www/i18next-parser.config.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
input: [
|
||||||
|
'api/**/*.{js,jsx,ts,tsx}',
|
||||||
|
'config/**/*.{js,jsx,ts,tsx}',
|
||||||
|
'components/**/*.{js,jsx,ts,tsx}',
|
||||||
|
'pages/**/*.{js,jsx,ts,tsx}',
|
||||||
|
],
|
||||||
|
output: 'i18n/locales/$LOCALE.json',
|
||||||
|
locales: ['en', 'zh'],
|
||||||
|
}
|
@@ -12,42 +12,73 @@ export const ZodPortSchema = z.coerce
|
|||||||
.min(1, { message: 'validation.portRange.min' })
|
.min(1, { message: 'validation.portRange.min' })
|
||||||
.max(65535, { message: 'validation.portRange.max' })
|
.max(65535, { message: 'validation.portRange.max' })
|
||||||
|
|
||||||
export const ZodIPSchema = z.string({ required_error: 'validation.required' })
|
export const ZodPortOptionalSchema = z.coerce
|
||||||
|
.number({ required_error: 'validation.required' })
|
||||||
|
.min(1, { message: 'validation.portRange.min' })
|
||||||
|
.max(65535, { message: 'validation.portRange.max' })
|
||||||
|
|
||||||
|
export const ZodIPSchema = z
|
||||||
|
.string({ required_error: 'validation.required' })
|
||||||
.regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, { message: 'validation.ipAddress' })
|
.regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, { message: 'validation.ipAddress' })
|
||||||
export const ZodStringSchema = z.string({ required_error: 'validation.required' })
|
export const ZodStringSchema = z
|
||||||
|
.string({ required_error: 'validation.required' })
|
||||||
.min(1, { message: 'validation.required' })
|
.min(1, { message: 'validation.required' })
|
||||||
|
|
||||||
export const ZodStringOptionalSchema = z.string().optional()
|
export const ZodStringOptionalSchema = z.string().optional()
|
||||||
export const ZodEmailSchema = z.string({ required_error: 'validation.required' })
|
export const ZodEmailSchema = z
|
||||||
|
.string({ required_error: 'validation.required' })
|
||||||
.min(1, { message: 'validation.required' })
|
.min(1, { message: 'validation.required' })
|
||||||
.email({ message: 'auth.email.invalid' })
|
.email({ message: 'auth.email.invalid' })
|
||||||
|
|
||||||
export const ConnectionProtocols = ["tcp", "kcp", "quic", "websocket", "wss"]
|
export const ConnectionProtocols = ['tcp', 'kcp', 'quic', 'websocket', 'wss']
|
||||||
|
|
||||||
export const TypedProxyConfigValid = (typedProxyCfg: TypedProxyConfig | undefined): boolean => {
|
export const TypedProxyConfigValid = (typedProxyCfg: TypedProxyConfig | undefined): boolean => {
|
||||||
return (typedProxyCfg?.localPort && typedProxyCfg.localIP && typedProxyCfg.name && typedProxyCfg.type) ? true : false
|
if (!typedProxyCfg) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typedProxyCfg.plugin && typedProxyCfg.plugin.type) {
|
||||||
|
if (typedProxyCfg.type === 'tcp' || typedProxyCfg.type === 'udp') {
|
||||||
|
if (!typedProxyCfg.remotePort) {
|
||||||
|
console.log('remotePort is undefined')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return typedProxyCfg.name && typedProxyCfg.type ? true : false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typedProxyCfg.type === 'tcp' || typedProxyCfg.type === 'udp') {
|
||||||
|
if (!typedProxyCfg.remotePort) {
|
||||||
|
console.log('remotePort is undefined')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return typedProxyCfg?.localPort && typedProxyCfg.localIP && typedProxyCfg.name && typedProxyCfg.type ? true : false
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IsIDValid = (clientID: string | undefined): boolean => {
|
export const IsIDValid = (clientID: string | undefined): boolean => {
|
||||||
if (clientID == undefined) {
|
if (clientID == undefined) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const regex = /^[a-zA-Z0-9-_]+$/;
|
const regex = /^[a-zA-Z0-9-_]+$/
|
||||||
return clientID.length > 0 && regex.test(clientID);
|
return clientID.length > 0 && regex.test(clientID)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClientConfigured = (client: Client | undefined): boolean => {
|
export const ClientConfigured = (client: Client | undefined): boolean => {
|
||||||
if (client == undefined) {
|
if (client == undefined) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return !((client.config == undefined || client.config == '') &&
|
return !(
|
||||||
(client.clientIds == undefined || client.clientIds.length == 0))
|
(client.config == undefined || client.config == '') &&
|
||||||
|
(client.clientIds == undefined || client.clientIds.length == 0)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// .refine((e) => e === "abcd@fg.com", "This email is not in our database")
|
// .refine((e) => e === "abcd@fg.com", "This email is not in our database")
|
||||||
|
|
||||||
export const ExecCommandStr = <T extends Client | Server>(
|
export const ExecCommandStr = <T extends Client | Server>(
|
||||||
type: "client" | "server",
|
type: 'client' | 'server',
|
||||||
item: T,
|
item: T,
|
||||||
info: GetPlatformInfoResponse,
|
info: GetPlatformInfoResponse,
|
||||||
fileName?: string,
|
fileName?: string,
|
||||||
@@ -56,37 +87,38 @@ export const ExecCommandStr = <T extends Client | Server>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const JoinCommandStr = (info: GetPlatformInfoResponse, token: string, fileName?: string, clientID?: string) => {
|
export const JoinCommandStr = (info: GetPlatformInfoResponse, token: string, fileName?: string, clientID?: string) => {
|
||||||
return `${fileName || 'frp-panel'} join${clientID ? ` -i ${clientID}` : ''} -j ${token} --api-url ${info.clientApiUrl} --rpc-url ${info.clientRpcUrl}`
|
return `${fileName || 'frp-panel'} join${clientID ? ` -i ${clientID}` : ''} -j ${token} --api-url ${info.clientApiUrl} --rpc-url ${info.clientRpcUrl}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WindowsInstallCommand = <T extends Client | Server>(
|
export const WindowsInstallCommand = <T extends Client | Server>(
|
||||||
type: "client" | "server",
|
type: 'client' | 'server',
|
||||||
item: T,
|
item: T,
|
||||||
info: GetPlatformInfoResponse,
|
info: GetPlatformInfoResponse,
|
||||||
|
github_proxy?: boolean,
|
||||||
) => {
|
) => {
|
||||||
return `[Net.ServicePointManager]::SecurityProtocol = ` +
|
return (
|
||||||
|
`[Net.ServicePointManager]::SecurityProtocol = ` +
|
||||||
`[Net.SecurityProtocolType]::Ssl3 -bor ` +
|
`[Net.SecurityProtocolType]::Ssl3 -bor ` +
|
||||||
`[Net.SecurityProtocolType]::Tls -bor ` +
|
`[Net.SecurityProtocolType]::Tls -bor ` +
|
||||||
`[Net.SecurityProtocolType]::Tls11 -bor ` +
|
`[Net.SecurityProtocolType]::Tls11 -bor ` +
|
||||||
`[Net.SecurityProtocolType]::Tls12;set-ExecutionPolicy RemoteSigned;` +
|
`[Net.SecurityProtocolType]::Tls12;set-ExecutionPolicy RemoteSigned;` +
|
||||||
`Invoke-WebRequest https://raw.githubusercontent.com/VaalaCat/frp-panel/main/install.ps1 ` +
|
`Invoke-WebRequest ${github_proxy ? info.githubProxyUrl : ''}https://raw.githubusercontent.com/VaalaCat/frp-panel/main/install.ps1 ` +
|
||||||
`-OutFile C:\install.ps1;powershell.exe C:\install.ps1 ${ExecCommandStr(type, item, info, ' ')}`
|
`-OutFile C:\install.ps1;powershell.exe C:\install.ps1 ${ExecCommandStr(type, item, info, ' ')}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LinuxInstallCommand = <T extends Client | Server>(
|
export const LinuxInstallCommand = <T extends Client | Server>(
|
||||||
type: "client" | "server",
|
type: 'client' | 'server',
|
||||||
item: T,
|
item: T,
|
||||||
info: GetPlatformInfoResponse,
|
info: GetPlatformInfoResponse,
|
||||||
|
github_proxy?: boolean,
|
||||||
) => {
|
) => {
|
||||||
return `curl -fSL https://raw.githubusercontent.com/VaalaCat/frp-panel/main/install.sh | bash -s --${ExecCommandStr(type, item, info, ' ')}`
|
return `curl -fSL ${github_proxy ? info.githubProxyUrl : ''}https://raw.githubusercontent.com/VaalaCat/frp-panel/main/install.sh | bash -s --${ExecCommandStr(type, item, info, ' ')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClientEnvFile = <T extends Client | Server>(
|
export const ClientEnvFile = <T extends Client | Server>(item: T, info: GetPlatformInfoResponse) => {
|
||||||
item: T,
|
|
||||||
info: GetPlatformInfoResponse,
|
|
||||||
) => {
|
|
||||||
return `CLIENT_ID=${item.id}
|
return `CLIENT_ID=${item.id}
|
||||||
CLIENT_SECRET=${item.secret}
|
CLIENT_SECRET=${item.secret}
|
||||||
CLIENT_API_URL=${info.clientApiUrl}
|
CLIENT_API_URL=${info.clientApiUrl}
|
||||||
CLIENT_RPC_URL=${info.clientRpcUrl}`
|
CLIENT_RPC_URL=${info.clientRpcUrl}`
|
||||||
}
|
}
|
||||||
|
@@ -391,6 +391,58 @@ export interface GetProxyConfigResponse {
|
|||||||
*/
|
*/
|
||||||
workingStatus?: ProxyWorkingStatus;
|
workingStatus?: ProxyWorkingStatus;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message api_client.StopProxyRequest
|
||||||
|
*/
|
||||||
|
export interface StopProxyRequest {
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: optional string client_id = 1;
|
||||||
|
*/
|
||||||
|
clientId?: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: optional string server_id = 2;
|
||||||
|
*/
|
||||||
|
serverId?: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: optional string name = 3;
|
||||||
|
*/
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message api_client.StopProxyResponse
|
||||||
|
*/
|
||||||
|
export interface StopProxyResponse {
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: optional common.Status status = 1;
|
||||||
|
*/
|
||||||
|
status?: Status;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message api_client.StartProxyRequest
|
||||||
|
*/
|
||||||
|
export interface StartProxyRequest {
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: optional string client_id = 1;
|
||||||
|
*/
|
||||||
|
clientId?: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: optional string server_id = 2;
|
||||||
|
*/
|
||||||
|
serverId?: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: optional string name = 3;
|
||||||
|
*/
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated from protobuf message api_client.StartProxyResponse
|
||||||
|
*/
|
||||||
|
export interface StartProxyResponse {
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: optional common.Status status = 1;
|
||||||
|
*/
|
||||||
|
status?: Status;
|
||||||
|
}
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
class InitClientRequest$Type extends MessageType<InitClientRequest> {
|
class InitClientRequest$Type extends MessageType<InitClientRequest> {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -1899,3 +1951,215 @@ class GetProxyConfigResponse$Type extends MessageType<GetProxyConfigResponse> {
|
|||||||
* @generated MessageType for protobuf message api_client.GetProxyConfigResponse
|
* @generated MessageType for protobuf message api_client.GetProxyConfigResponse
|
||||||
*/
|
*/
|
||||||
export const GetProxyConfigResponse = new GetProxyConfigResponse$Type();
|
export const GetProxyConfigResponse = new GetProxyConfigResponse$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class StopProxyRequest$Type extends MessageType<StopProxyRequest> {
|
||||||
|
constructor() {
|
||||||
|
super("api_client.StopProxyRequest", [
|
||||||
|
{ no: 1, name: "client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 2, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 3, name: "name", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<StopProxyRequest>): StopProxyRequest {
|
||||||
|
const message = globalThis.Object.create((this.messagePrototype!));
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<StopProxyRequest>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: StopProxyRequest): StopProxyRequest {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* optional string client_id */ 1:
|
||||||
|
message.clientId = reader.string();
|
||||||
|
break;
|
||||||
|
case /* optional string server_id */ 2:
|
||||||
|
message.serverId = reader.string();
|
||||||
|
break;
|
||||||
|
case /* optional string name */ 3:
|
||||||
|
message.name = reader.string();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
let u = options.readUnknownField;
|
||||||
|
if (u === "throw")
|
||||||
|
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||||
|
let d = reader.skip(wireType);
|
||||||
|
if (u !== false)
|
||||||
|
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryWrite(message: StopProxyRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* optional string client_id = 1; */
|
||||||
|
if (message.clientId !== undefined)
|
||||||
|
writer.tag(1, WireType.LengthDelimited).string(message.clientId);
|
||||||
|
/* optional string server_id = 2; */
|
||||||
|
if (message.serverId !== undefined)
|
||||||
|
writer.tag(2, WireType.LengthDelimited).string(message.serverId);
|
||||||
|
/* optional string name = 3; */
|
||||||
|
if (message.name !== undefined)
|
||||||
|
writer.tag(3, WireType.LengthDelimited).string(message.name);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message api_client.StopProxyRequest
|
||||||
|
*/
|
||||||
|
export const StopProxyRequest = new StopProxyRequest$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class StopProxyResponse$Type extends MessageType<StopProxyResponse> {
|
||||||
|
constructor() {
|
||||||
|
super("api_client.StopProxyResponse", [
|
||||||
|
{ no: 1, name: "status", kind: "message", T: () => Status }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<StopProxyResponse>): StopProxyResponse {
|
||||||
|
const message = globalThis.Object.create((this.messagePrototype!));
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<StopProxyResponse>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: StopProxyResponse): StopProxyResponse {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* optional common.Status status */ 1:
|
||||||
|
message.status = Status.internalBinaryRead(reader, reader.uint32(), options, message.status);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
let u = options.readUnknownField;
|
||||||
|
if (u === "throw")
|
||||||
|
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||||
|
let d = reader.skip(wireType);
|
||||||
|
if (u !== false)
|
||||||
|
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryWrite(message: StopProxyResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* optional common.Status status = 1; */
|
||||||
|
if (message.status)
|
||||||
|
Status.internalBinaryWrite(message.status, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message api_client.StopProxyResponse
|
||||||
|
*/
|
||||||
|
export const StopProxyResponse = new StopProxyResponse$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class StartProxyRequest$Type extends MessageType<StartProxyRequest> {
|
||||||
|
constructor() {
|
||||||
|
super("api_client.StartProxyRequest", [
|
||||||
|
{ no: 1, name: "client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 2, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 3, name: "name", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<StartProxyRequest>): StartProxyRequest {
|
||||||
|
const message = globalThis.Object.create((this.messagePrototype!));
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<StartProxyRequest>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: StartProxyRequest): StartProxyRequest {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* optional string client_id */ 1:
|
||||||
|
message.clientId = reader.string();
|
||||||
|
break;
|
||||||
|
case /* optional string server_id */ 2:
|
||||||
|
message.serverId = reader.string();
|
||||||
|
break;
|
||||||
|
case /* optional string name */ 3:
|
||||||
|
message.name = reader.string();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
let u = options.readUnknownField;
|
||||||
|
if (u === "throw")
|
||||||
|
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||||
|
let d = reader.skip(wireType);
|
||||||
|
if (u !== false)
|
||||||
|
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryWrite(message: StartProxyRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* optional string client_id = 1; */
|
||||||
|
if (message.clientId !== undefined)
|
||||||
|
writer.tag(1, WireType.LengthDelimited).string(message.clientId);
|
||||||
|
/* optional string server_id = 2; */
|
||||||
|
if (message.serverId !== undefined)
|
||||||
|
writer.tag(2, WireType.LengthDelimited).string(message.serverId);
|
||||||
|
/* optional string name = 3; */
|
||||||
|
if (message.name !== undefined)
|
||||||
|
writer.tag(3, WireType.LengthDelimited).string(message.name);
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message api_client.StartProxyRequest
|
||||||
|
*/
|
||||||
|
export const StartProxyRequest = new StartProxyRequest$Type();
|
||||||
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
|
class StartProxyResponse$Type extends MessageType<StartProxyResponse> {
|
||||||
|
constructor() {
|
||||||
|
super("api_client.StartProxyResponse", [
|
||||||
|
{ no: 1, name: "status", kind: "message", T: () => Status }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
create(value?: PartialMessage<StartProxyResponse>): StartProxyResponse {
|
||||||
|
const message = globalThis.Object.create((this.messagePrototype!));
|
||||||
|
if (value !== undefined)
|
||||||
|
reflectionMergePartial<StartProxyResponse>(this, message, value);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: StartProxyResponse): StartProxyResponse {
|
||||||
|
let message = target ?? this.create(), end = reader.pos + length;
|
||||||
|
while (reader.pos < end) {
|
||||||
|
let [fieldNo, wireType] = reader.tag();
|
||||||
|
switch (fieldNo) {
|
||||||
|
case /* optional common.Status status */ 1:
|
||||||
|
message.status = Status.internalBinaryRead(reader, reader.uint32(), options, message.status);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
let u = options.readUnknownField;
|
||||||
|
if (u === "throw")
|
||||||
|
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||||
|
let d = reader.skip(wireType);
|
||||||
|
if (u !== false)
|
||||||
|
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
internalBinaryWrite(message: StartProxyResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||||
|
/* optional common.Status status = 1; */
|
||||||
|
if (message.status)
|
||||||
|
Status.internalBinaryWrite(message.status, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||||
|
let u = options.writeUnknownFields;
|
||||||
|
if (u !== false)
|
||||||
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @generated MessageType for protobuf message api_client.StartProxyResponse
|
||||||
|
*/
|
||||||
|
export const StartProxyResponse = new StartProxyResponse$Type();
|
||||||
|
@@ -109,6 +109,10 @@ export interface GetPlatformInfoResponse {
|
|||||||
* @generated from protobuf field: string client_api_url = 13;
|
* @generated from protobuf field: string client_api_url = 13;
|
||||||
*/
|
*/
|
||||||
clientApiUrl: string;
|
clientApiUrl: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: string github_proxy_url = 14;
|
||||||
|
*/
|
||||||
|
githubProxyUrl: string;
|
||||||
}
|
}
|
||||||
// @generated message type with reflection information, may provide speed optimized methods
|
// @generated message type with reflection information, may provide speed optimized methods
|
||||||
class GetUserInfoRequest$Type extends MessageType<GetUserInfoRequest> {
|
class GetUserInfoRequest$Type extends MessageType<GetUserInfoRequest> {
|
||||||
@@ -321,7 +325,8 @@ class GetPlatformInfoResponse$Type extends MessageType<GetPlatformInfoResponse>
|
|||||||
{ no: 10, name: "master_api_port", kind: "scalar", T: 5 /*ScalarType.INT32*/ },
|
{ no: 10, name: "master_api_port", kind: "scalar", T: 5 /*ScalarType.INT32*/ },
|
||||||
{ no: 11, name: "master_api_scheme", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
{ no: 11, name: "master_api_scheme", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 12, name: "client_rpc_url", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
{ no: 12, name: "client_rpc_url", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 13, name: "client_api_url", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
{ no: 13, name: "client_api_url", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 14, name: "github_proxy_url", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
create(value?: PartialMessage<GetPlatformInfoResponse>): GetPlatformInfoResponse {
|
create(value?: PartialMessage<GetPlatformInfoResponse>): GetPlatformInfoResponse {
|
||||||
@@ -338,6 +343,7 @@ class GetPlatformInfoResponse$Type extends MessageType<GetPlatformInfoResponse>
|
|||||||
message.masterApiScheme = "";
|
message.masterApiScheme = "";
|
||||||
message.clientRpcUrl = "";
|
message.clientRpcUrl = "";
|
||||||
message.clientApiUrl = "";
|
message.clientApiUrl = "";
|
||||||
|
message.githubProxyUrl = "";
|
||||||
if (value !== undefined)
|
if (value !== undefined)
|
||||||
reflectionMergePartial<GetPlatformInfoResponse>(this, message, value);
|
reflectionMergePartial<GetPlatformInfoResponse>(this, message, value);
|
||||||
return message;
|
return message;
|
||||||
@@ -386,6 +392,9 @@ class GetPlatformInfoResponse$Type extends MessageType<GetPlatformInfoResponse>
|
|||||||
case /* string client_api_url */ 13:
|
case /* string client_api_url */ 13:
|
||||||
message.clientApiUrl = reader.string();
|
message.clientApiUrl = reader.string();
|
||||||
break;
|
break;
|
||||||
|
case /* string github_proxy_url */ 14:
|
||||||
|
message.githubProxyUrl = reader.string();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
let u = options.readUnknownField;
|
let u = options.readUnknownField;
|
||||||
if (u === "throw")
|
if (u === "throw")
|
||||||
@@ -437,6 +446,9 @@ class GetPlatformInfoResponse$Type extends MessageType<GetPlatformInfoResponse>
|
|||||||
/* string client_api_url = 13; */
|
/* string client_api_url = 13; */
|
||||||
if (message.clientApiUrl !== "")
|
if (message.clientApiUrl !== "")
|
||||||
writer.tag(13, WireType.LengthDelimited).string(message.clientApiUrl);
|
writer.tag(13, WireType.LengthDelimited).string(message.clientApiUrl);
|
||||||
|
/* string github_proxy_url = 14; */
|
||||||
|
if (message.githubProxyUrl !== "")
|
||||||
|
writer.tag(14, WireType.LengthDelimited).string(message.githubProxyUrl);
|
||||||
let u = options.writeUnknownFields;
|
let u = options.writeUnknownFields;
|
||||||
if (u !== false)
|
if (u !== false)
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
@@ -233,6 +233,10 @@ export interface ProxyConfig {
|
|||||||
* @generated from protobuf field: optional string origin_client_id = 7;
|
* @generated from protobuf field: optional string origin_client_id = 7;
|
||||||
*/
|
*/
|
||||||
originClientId?: string;
|
originClientId?: string;
|
||||||
|
/**
|
||||||
|
* @generated from protobuf field: optional bool stopped = 8;
|
||||||
|
*/
|
||||||
|
stopped?: boolean;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @generated from protobuf message common.ProxyWorkingStatus
|
* @generated from protobuf message common.ProxyWorkingStatus
|
||||||
@@ -869,7 +873,8 @@ class ProxyConfig$Type extends MessageType<ProxyConfig> {
|
|||||||
{ no: 4, name: "client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
{ no: 4, name: "client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 5, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
{ no: 5, name: "server_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 6, name: "config", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
{ no: 6, name: "config", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
{ no: 7, name: "origin_client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }
|
{ no: 7, name: "origin_client_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ },
|
||||||
|
{ no: 8, name: "stopped", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
create(value?: PartialMessage<ProxyConfig>): ProxyConfig {
|
create(value?: PartialMessage<ProxyConfig>): ProxyConfig {
|
||||||
@@ -904,6 +909,9 @@ class ProxyConfig$Type extends MessageType<ProxyConfig> {
|
|||||||
case /* optional string origin_client_id */ 7:
|
case /* optional string origin_client_id */ 7:
|
||||||
message.originClientId = reader.string();
|
message.originClientId = reader.string();
|
||||||
break;
|
break;
|
||||||
|
case /* optional bool stopped */ 8:
|
||||||
|
message.stopped = reader.bool();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
let u = options.readUnknownField;
|
let u = options.readUnknownField;
|
||||||
if (u === "throw")
|
if (u === "throw")
|
||||||
@@ -937,6 +945,9 @@ class ProxyConfig$Type extends MessageType<ProxyConfig> {
|
|||||||
/* optional string origin_client_id = 7; */
|
/* optional string origin_client_id = 7; */
|
||||||
if (message.originClientId !== undefined)
|
if (message.originClientId !== undefined)
|
||||||
writer.tag(7, WireType.LengthDelimited).string(message.originClientId);
|
writer.tag(7, WireType.LengthDelimited).string(message.originClientId);
|
||||||
|
/* optional bool stopped = 8; */
|
||||||
|
if (message.stopped !== undefined)
|
||||||
|
writer.tag(8, WireType.Varint).bool(message.stopped);
|
||||||
let u = options.writeUnknownFields;
|
let u = options.writeUnknownFields;
|
||||||
if (u !== false)
|
if (u !== false)
|
||||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||||
|
@@ -6,7 +6,8 @@
|
|||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build && rm -rf ../cmd/frpp/out && cp -r out ../cmd/frpp",
|
"build": "next build && rm -rf ../cmd/frpp/out && cp -r out ../cmd/frpp",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"i18n:extract": "pnpx i18next-parser"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
"@protobuf-ts/runtime": "^2.9.3",
|
"@protobuf-ts/runtime": "^2.9.3",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-avatar": "^1.0.4",
|
"@radix-ui/react-avatar": "^1.0.4",
|
||||||
|
"@radix-ui/react-checkbox": "^1.2.3",
|
||||||
"@radix-ui/react-collapsible": "^1.1.1",
|
"@radix-ui/react-collapsible": "^1.1.1",
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
@@ -75,8 +77,9 @@
|
|||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-next": "14.0.4",
|
"eslint-config-next": "14.0.4",
|
||||||
|
"i18next-parser": "^9.3.0",
|
||||||
"postcss": "^8.4.33",
|
"postcss": "^8.4.33",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -39,8 +39,7 @@ export default function Test() {
|
|||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
return (
|
return (
|
||||||
<>
|
<></>
|
||||||
</>
|
|
||||||
// <Providers>
|
// <Providers>
|
||||||
// <RootLayout mainHeader={<Header />}>
|
// <RootLayout mainHeader={<Header />}>
|
||||||
// <div className="w-full">
|
// <div className="w-full">
|
||||||
|
1292
www/pnpm-lock.yaml
generated
1292
www/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -10,11 +10,12 @@ export const $token = persistentAtom<string | undefined>(LOCAL_STORAGE_TOKEN_KEY
|
|||||||
export const $platformInfo = atom<GetPlatformInfoResponse | undefined>()
|
export const $platformInfo = atom<GetPlatformInfoResponse | undefined>()
|
||||||
|
|
||||||
// 创建持久化的语言设置
|
// 创建持久化的语言设置
|
||||||
export const $language = persistentAtom<string>(
|
export const $language = persistentAtom<string>('user-language', 'zh', {
|
||||||
'user-language',
|
encode: JSON.stringify,
|
||||||
'zh',
|
decode: JSON.parse,
|
||||||
{
|
})
|
||||||
encode: JSON.stringify,
|
|
||||||
decode: JSON.parse
|
export const $useServerGithubProxyUrl = persistentAtom<boolean>('use_server_github_proxy_url', false, {
|
||||||
}
|
encode: JSON.stringify,
|
||||||
)
|
decode: JSON.parse,
|
||||||
|
})
|
||||||
|
@@ -2,26 +2,35 @@ import { HeaderOperations } from './common'
|
|||||||
|
|
||||||
export interface ClientPluginOptions {}
|
export interface ClientPluginOptions {}
|
||||||
|
|
||||||
|
export type ClientPluginType =
|
||||||
|
| 'http_proxy'
|
||||||
|
| 'http2https'
|
||||||
|
| 'https2http'
|
||||||
|
| 'https2https'
|
||||||
|
| 'socks5'
|
||||||
|
| 'static_file'
|
||||||
|
| 'unix_domain_socket'
|
||||||
|
|
||||||
export interface TypedClientPluginOptions {
|
export interface TypedClientPluginOptions {
|
||||||
type: string
|
type: ClientPluginType
|
||||||
clientPluginOptions?: ClientPluginOptions
|
clientPluginOptions?: ClientPluginOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HTTP2HTTPSPluginOptions {
|
export interface HTTP2HTTPSPluginOptions {
|
||||||
type?: string
|
type: 'http2https'
|
||||||
localAddr?: string
|
localAddr?: string
|
||||||
hostHeaderRewrite?: string
|
hostHeaderRewrite?: string
|
||||||
requestHeaders?: HeaderOperations
|
requestHeaders?: HeaderOperations
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HTTPProxyPluginOptions {
|
export interface HTTPProxyPluginOptions {
|
||||||
type?: string
|
type: 'http_proxy'
|
||||||
httpUser?: string
|
httpUser?: string
|
||||||
httpPassword?: string
|
httpPassword?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HTTPS2HTTPPluginOptions {
|
export interface HTTPS2HTTPPluginOptions {
|
||||||
type?: string
|
type: 'https2http'
|
||||||
localAddr?: string
|
localAddr?: string
|
||||||
hostHeaderRewrite?: string
|
hostHeaderRewrite?: string
|
||||||
requestHeaders?: HeaderOperations
|
requestHeaders?: HeaderOperations
|
||||||
@@ -30,7 +39,7 @@ export interface HTTPS2HTTPPluginOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface HTTPS2HTTPSPluginOptions {
|
export interface HTTPS2HTTPSPluginOptions {
|
||||||
type?: string
|
type: 'https2https'
|
||||||
localAddr?: string
|
localAddr?: string
|
||||||
hostHeaderRewrite?: string
|
hostHeaderRewrite?: string
|
||||||
requestHeaders?: HeaderOperations
|
requestHeaders?: HeaderOperations
|
||||||
@@ -39,13 +48,13 @@ export interface HTTPS2HTTPSPluginOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Socks5PluginOptions {
|
export interface Socks5PluginOptions {
|
||||||
type?: string
|
type: 'socks5'
|
||||||
username?: string
|
username?: string
|
||||||
password?: string
|
password?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StaticFilePluginOptions {
|
export interface StaticFilePluginOptions {
|
||||||
type?: string
|
type: 'static_file'
|
||||||
localPath?: string
|
localPath?: string
|
||||||
stripPrefix?: string
|
stripPrefix?: string
|
||||||
httpUser?: string
|
httpUser?: string
|
||||||
@@ -53,6 +62,6 @@ export interface StaticFilePluginOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface UnixDomainSocketPluginOptions {
|
export interface UnixDomainSocketPluginOptions {
|
||||||
type?: string
|
type: 'unix_domain_socket'
|
||||||
unixPath?: string
|
unixPath?: string
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user