mirror of
https://github.com/fatedier/frp.git
synced 2025-04-23 01:31:26 +00:00
commit
31b44c1feb
@ -2,7 +2,7 @@ version: 2
|
|||||||
jobs:
|
jobs:
|
||||||
go-version-latest:
|
go-version-latest:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.22-node
|
- image: cimg/go:1.23-node
|
||||||
resource_class: large
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
8
.github/workflows/stale.yml
vendored
8
.github/workflows/stale.yml
vendored
@ -21,14 +21,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
stale-issue-message: 'Issues go stale after 21d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
|
stale-issue-message: 'Issues go stale after 14d of inactivity. Stale issues rot after an additional 3d of inactivity and eventually close.'
|
||||||
stale-pr-message: "PRs go stale after 21d of inactivity. Stale PRs rot after an additional 7d of inactivity and eventually close."
|
stale-pr-message: "PRs go stale after 14d of inactivity. Stale PRs rot after an additional 3d of inactivity and eventually close."
|
||||||
stale-issue-label: 'lifecycle/stale'
|
stale-issue-label: 'lifecycle/stale'
|
||||||
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
||||||
stale-pr-label: 'lifecycle/stale'
|
stale-pr-label: 'lifecycle/stale'
|
||||||
exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
||||||
days-before-stale: 21
|
days-before-stale: 14
|
||||||
days-before-close: 7
|
days-before-close: 3
|
||||||
debug-only: ${{ github.event.inputs.debug-only }}
|
debug-only: ${{ github.event.inputs.debug-only }}
|
||||||
exempt-all-pr-milestones: true
|
exempt-all-pr-milestones: true
|
||||||
exempt-all-pr-assignees: true
|
exempt-all-pr-assignees: true
|
||||||
|
@ -48,7 +48,8 @@ linters-settings:
|
|||||||
check-blank: false
|
check-blank: false
|
||||||
govet:
|
govet:
|
||||||
# report about shadowed variables
|
# report about shadowed variables
|
||||||
check-shadowing: false
|
disable:
|
||||||
|
- shadow
|
||||||
maligned:
|
maligned:
|
||||||
# print struct with more effective memory layout or not, false by default
|
# print struct with more effective memory layout or not, false by default
|
||||||
suggest-new: true
|
suggest-new: true
|
||||||
|
@ -97,7 +97,7 @@ frp also offers a P2P connect mode.
|
|||||||
* [Client Plugins](#client-plugins)
|
* [Client Plugins](#client-plugins)
|
||||||
* [Server Manage Plugins](#server-manage-plugins)
|
* [Server Manage Plugins](#server-manage-plugins)
|
||||||
* [SSH Tunnel Gateway](#ssh-tunnel-gateway)
|
* [SSH Tunnel Gateway](#ssh-tunnel-gateway)
|
||||||
* [Releated Projects](#releated-projects)
|
* [Related Projects](#related-projects)
|
||||||
* [Contributing](#contributing)
|
* [Contributing](#contributing)
|
||||||
* [Donation](#donation)
|
* [Donation](#donation)
|
||||||
* [GitHub Sponsors](#github-sponsors)
|
* [GitHub Sponsors](#github-sponsors)
|
||||||
@ -1260,7 +1260,7 @@ frpc tcp --proxy_name "test-tcp" --local_ip 127.0.0.1 --local_port 8080 --remote
|
|||||||
|
|
||||||
Please refer to this [document](/doc/ssh_tunnel_gateway.md) for more information.
|
Please refer to this [document](/doc/ssh_tunnel_gateway.md) for more information.
|
||||||
|
|
||||||
## Releated Projects
|
## Related Projects
|
||||||
|
|
||||||
* [gofrp/plugin](https://github.com/gofrp/plugin) - A repository for frp plugins that contains a variety of plugins implemented based on the frp extension mechanism, meeting the customization needs of different scenarios.
|
* [gofrp/plugin](https://github.com/gofrp/plugin) - A repository for frp plugins that contains a variety of plugins implemented based on the frp extension mechanism, meeting the customization needs of different scenarios.
|
||||||
* [gofrp/tiny-frpc](https://github.com/gofrp/tiny-frpc) - A lightweight version of the frp client (around 3.5MB at minimum) implemented using the ssh protocol, supporting some of the most commonly used features, suitable for devices with limited resources.
|
* [gofrp/tiny-frpc](https://github.com/gofrp/tiny-frpc) - A lightweight version of the frp client (around 3.5MB at minimum) implemented using the ssh protocol, supporting some of the most commonly used features, suitable for devices with limited resources.
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
### Features
|
### Features
|
||||||
|
|
||||||
* `tzdata` is installed by default in the container image, and the time zone can be set using the `TZ` environment variable.
|
* Support metadatas and annotations in frpc proxy commands.
|
||||||
* The `quic-bind-port` command line parameter is supported in frps, which specifies the port for accepting frpc connections using the QUIC protocol.
|
|
||||||
* The vhost HTTP proxy of frps supports the h2c protocol.
|
### Fixes
|
||||||
|
|
||||||
|
* Properly release resources in service.Close() to prevent resource leaks when used as a library.
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
var ErrPayloadType = errors.New("error payload type")
|
var ErrPayloadType = errors.New("error payload type")
|
||||||
|
|
||||||
type Handler func(payload interface{}) error
|
type Handler func(payload any) error
|
||||||
|
|
||||||
type StartProxyPayload struct {
|
type StartProxyPayload struct {
|
||||||
NewProxyMsg *msg.NewProxy
|
NewProxyMsg *msg.NewProxy
|
||||||
|
@ -96,7 +96,7 @@ func (pm *Manager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *Manager) HandleEvent(payload interface{}) error {
|
func (pm *Manager) HandleEvent(payload any) error {
|
||||||
var m msg.Message
|
var m msg.Message
|
||||||
switch e := payload.(type) {
|
switch e := payload.(type) {
|
||||||
case *event.StartProxyPayload:
|
case *event.StartProxyPayload:
|
||||||
|
4
go.mod
4
go.mod
@ -1,11 +1,11 @@
|
|||||||
module github.com/fatedier/frp
|
module github.com/fatedier/frp
|
||||||
|
|
||||||
go 1.22.0
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||||
github.com/coreos/go-oidc/v3 v3.10.0
|
github.com/coreos/go-oidc/v3 v3.10.0
|
||||||
github.com/fatedier/golib v0.5.0
|
github.com/fatedier/golib v0.5.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
4
go.sum
4
go.sum
@ -21,8 +21,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatedier/golib v0.5.0 h1:hNcH7hgfIFqVWbP+YojCCAj4eO94pPf4dEF8lmq2jWs=
|
github.com/fatedier/golib v0.5.1 h1:hcKAnaw5mdI/1KWRGejxR+i1Hn/NvbY5UsMKDr7o13M=
|
||||||
github.com/fatedier/golib v0.5.0/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ=
|
github.com/fatedier/golib v0.5.1/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ=
|
||||||
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
|
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
|
||||||
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
||||||
|
@ -106,6 +106,8 @@ func registerProxyBaseConfigFlags(cmd *cobra.Command, c *v1.ProxyBaseConfig, opt
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&c.Name, "proxy_name", "n", "", "proxy name")
|
cmd.Flags().StringVarP(&c.Name, "proxy_name", "n", "", "proxy name")
|
||||||
|
cmd.Flags().StringToStringVarP(&c.Metadatas, "metadatas", "", nil, "metadata key-value pairs (e.g., key1=value1,key2=value2)")
|
||||||
|
cmd.Flags().StringToStringVarP(&c.Annotations, "annotations", "", nil, "annotation key-value pairs (e.g., key1=value1,key2=value2)")
|
||||||
|
|
||||||
if !options.sshMode {
|
if !options.sshMode {
|
||||||
cmd.Flags().StringVarP(&c.LocalIP, "local_ip", "i", "127.0.0.1", "local ip")
|
cmd.Flags().StringVarP(&c.LocalIP, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
|
@ -170,7 +170,7 @@ type ClientCommonConf struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Supported sources including: string(file path), []byte, Reader interface.
|
// Supported sources including: string(file path), []byte, Reader interface.
|
||||||
func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
|
func UnmarshalClientConfFromIni(source any) (ClientCommonConf, error) {
|
||||||
f, err := ini.LoadSources(ini.LoadOptions{
|
f, err := ini.LoadSources(ini.LoadOptions{
|
||||||
Insensitive: false,
|
Insensitive: false,
|
||||||
InsensitiveSections: false,
|
InsensitiveSections: false,
|
||||||
@ -203,7 +203,7 @@ func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
|
|||||||
// otherwise just start proxies in startProxy map
|
// otherwise just start proxies in startProxy map
|
||||||
func LoadAllProxyConfsFromIni(
|
func LoadAllProxyConfsFromIni(
|
||||||
prefix string,
|
prefix string,
|
||||||
source interface{},
|
source any,
|
||||||
start []string,
|
start []string,
|
||||||
) (map[string]ProxyConf, map[string]VisitorConf, error) {
|
) (map[string]ProxyConf, map[string]VisitorConf, error) {
|
||||||
f, err := ini.LoadSources(ini.LoadOptions{
|
f, err := ini.LoadSources(ini.LoadOptions{
|
||||||
|
@ -217,7 +217,7 @@ func GetDefaultServerConf() ServerCommonConf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
|
func UnmarshalServerConfFromIni(source any) (ServerCommonConf, error) {
|
||||||
f, err := ini.LoadSources(ini.LoadOptions{
|
f, err := ini.LoadSources(ini.LoadOptions{
|
||||||
Insensitive: false,
|
Insensitive: false,
|
||||||
InsensitiveSections: false,
|
InsensitiveSections: false,
|
||||||
|
@ -18,10 +18,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
toml "github.com/pelletier/go-toml/v2"
|
toml "github.com/pelletier/go-toml/v2"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
@ -118,7 +118,7 @@ func LoadConfigure(b []byte, c any, strict bool) error {
|
|||||||
defer v1.DisallowUnknownFieldsMu.Unlock()
|
defer v1.DisallowUnknownFieldsMu.Unlock()
|
||||||
v1.DisallowUnknownFields = strict
|
v1.DisallowUnknownFields = strict
|
||||||
|
|
||||||
var tomlObj interface{}
|
var tomlObj any
|
||||||
// Try to unmarshal as TOML first; swallow errors from that (assume it's not valid TOML).
|
// Try to unmarshal as TOML first; swallow errors from that (assume it's not valid TOML).
|
||||||
if err := toml.Unmarshal(b, &tomlObj); err == nil {
|
if err := toml.Unmarshal(b, &tomlObj); err == nil {
|
||||||
b, err = json.Marshal(&tomlObj)
|
b, err = json.Marshal(&tomlObj)
|
||||||
|
@ -112,6 +112,29 @@ func TestLoadServerConfigStrictMode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderWithTemplate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"toml", tomlServerContent, tomlServerContent},
|
||||||
|
{"yaml", yamlServerContent, yamlServerContent},
|
||||||
|
{"json", jsonServerContent, jsonServerContent},
|
||||||
|
{"template numeric", `key = {{ 123 }}`, "key = 123"},
|
||||||
|
{"template string", `key = {{ "xyz" }}`, "key = xyz"},
|
||||||
|
{"template quote", `key = {{ printf "%q" "with space" }}`, `key = "with space"`},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
got, err := RenderWithTemplate([]byte(test.content), nil)
|
||||||
|
require.NoError(err)
|
||||||
|
require.EqualValues(test.want, string(got))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCustomStructStrictMode(t *testing.T) {
|
func TestCustomStructStrictMode(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
|
@ -39,6 +39,6 @@ func ReadMsgInto(c io.Reader, msg Message) (err error) {
|
|||||||
return msgCtl.ReadMsgInto(c, msg)
|
return msgCtl.ReadMsgInto(c, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteMsg(c io.Writer, msg interface{}) (err error) {
|
func WriteMsg(c io.Writer, msg any) (err error) {
|
||||||
return msgCtl.WriteMsg(c, msg)
|
return msgCtl.WriteMsg(c, msg)
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ const (
|
|||||||
TypeNatHoleReport = '6'
|
TypeNatHoleReport = '6'
|
||||||
)
|
)
|
||||||
|
|
||||||
var msgTypeMap = map[byte]interface{}{
|
var msgTypeMap = map[byte]any{
|
||||||
TypeLogin: Login{},
|
TypeLogin: Login{},
|
||||||
TypeLoginResp: LoginResp{},
|
TypeLoginResp: LoginResp{},
|
||||||
TypeNewProxy: NewProxy{},
|
TypeNewProxy: NewProxy{},
|
||||||
|
@ -72,7 +72,7 @@ func (p *httpPlugin) IsSupport(op string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *httpPlugin) Handle(ctx context.Context, op string, content interface{}) (*Response, interface{}, error) {
|
func (p *httpPlugin) Handle(ctx context.Context, op string, content any) (*Response, any, error) {
|
||||||
r := &Request{
|
r := &Request{
|
||||||
Version: APIVersion,
|
Version: APIVersion,
|
||||||
Op: op,
|
Op: op,
|
||||||
|
@ -75,7 +75,7 @@ func (m *Manager) Login(content *LoginContent) (*LoginContent, error) {
|
|||||||
Reject: false,
|
Reject: false,
|
||||||
Unchange: true,
|
Unchange: true,
|
||||||
}
|
}
|
||||||
retContent interface{}
|
retContent any
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
reqid, _ := util.RandID()
|
reqid, _ := util.RandID()
|
||||||
@ -109,7 +109,7 @@ func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent, error) {
|
|||||||
Reject: false,
|
Reject: false,
|
||||||
Unchange: true,
|
Unchange: true,
|
||||||
}
|
}
|
||||||
retContent interface{}
|
retContent any
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
reqid, _ := util.RandID()
|
reqid, _ := util.RandID()
|
||||||
@ -168,7 +168,7 @@ func (m *Manager) Ping(content *PingContent) (*PingContent, error) {
|
|||||||
Reject: false,
|
Reject: false,
|
||||||
Unchange: true,
|
Unchange: true,
|
||||||
}
|
}
|
||||||
retContent interface{}
|
retContent any
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
reqid, _ := util.RandID()
|
reqid, _ := util.RandID()
|
||||||
@ -202,7 +202,7 @@ func (m *Manager) NewWorkConn(content *NewWorkConnContent) (*NewWorkConnContent,
|
|||||||
Reject: false,
|
Reject: false,
|
||||||
Unchange: true,
|
Unchange: true,
|
||||||
}
|
}
|
||||||
retContent interface{}
|
retContent any
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
reqid, _ := util.RandID()
|
reqid, _ := util.RandID()
|
||||||
@ -236,7 +236,7 @@ func (m *Manager) NewUserConn(content *NewUserConnContent) (*NewUserConnContent,
|
|||||||
Reject: false,
|
Reject: false,
|
||||||
Unchange: true,
|
Unchange: true,
|
||||||
}
|
}
|
||||||
retContent interface{}
|
retContent any
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
reqid, _ := util.RandID()
|
reqid, _ := util.RandID()
|
||||||
|
@ -32,5 +32,5 @@ const (
|
|||||||
type Plugin interface {
|
type Plugin interface {
|
||||||
Name() string
|
Name() string
|
||||||
IsSupport(op string) bool
|
IsSupport(op string) bool
|
||||||
Handle(ctx context.Context, op string, content interface{}) (res *Response, retContent interface{}, err error)
|
Handle(ctx context.Context, op string, content any) (res *Response, retContent any, err error)
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,14 @@ import (
|
|||||||
type Request struct {
|
type Request struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Op string `json:"op"`
|
Op string `json:"op"`
|
||||||
Content interface{} `json:"content"`
|
Content any `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Reject bool `json:"reject"`
|
Reject bool `json:"reject"`
|
||||||
RejectReason string `json:"reject_reason"`
|
RejectReason string `json:"reject_reason"`
|
||||||
Unchange bool `json:"unchange"`
|
Unchange bool `json:"unchange"`
|
||||||
Content interface{} `json:"content"`
|
Content any `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginContent struct {
|
type LoginContent struct {
|
||||||
|
@ -112,6 +112,10 @@ func (g *Gateway) Run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) Close() error {
|
||||||
|
return g.ln.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Gateway) handleConn(conn net.Conn) {
|
func (g *Gateway) handleConn(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
@ -67,27 +67,27 @@ func InitLogger(logPath string, levelStr string, maxDays int, disableLogColor bo
|
|||||||
Logger = Logger.WithOptions(options...)
|
Logger = Logger.WithOptions(options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Errorf(format string, v ...interface{}) {
|
func Errorf(format string, v ...any) {
|
||||||
Logger.Errorf(format, v...)
|
Logger.Errorf(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warnf(format string, v ...interface{}) {
|
func Warnf(format string, v ...any) {
|
||||||
Logger.Warnf(format, v...)
|
Logger.Warnf(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Infof(format string, v ...interface{}) {
|
func Infof(format string, v ...any) {
|
||||||
Logger.Infof(format, v...)
|
Logger.Infof(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debugf(format string, v ...interface{}) {
|
func Debugf(format string, v ...any) {
|
||||||
Logger.Debugf(format, v...)
|
Logger.Debugf(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Tracef(format string, v ...interface{}) {
|
func Tracef(format string, v ...any) {
|
||||||
Logger.Tracef(format, v...)
|
Logger.Tracef(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Logf(level log.Level, offset int, format string, v ...interface{}) {
|
func Logf(level log.Level, offset int, format string, v ...any) {
|
||||||
Logger.Logf(level, offset, format, v...)
|
Logger.Logf(level, offset, format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package version
|
package version
|
||||||
|
|
||||||
var version = "0.61.1"
|
var version = "0.61.2"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
@ -24,7 +24,7 @@ type Router struct {
|
|||||||
httpUser string
|
httpUser string
|
||||||
|
|
||||||
// store any object here
|
// store any object here
|
||||||
payload interface{}
|
payload any
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouters() *Routers {
|
func NewRouters() *Routers {
|
||||||
@ -33,7 +33,7 @@ func NewRouters() *Routers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Routers) Add(domain, location, httpUser string, payload interface{}) error {
|
func (r *Routers) Add(domain, location, httpUser string, payload any) error {
|
||||||
domain = strings.ToLower(domain)
|
domain = strings.ToLower(domain)
|
||||||
|
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
|
@ -100,6 +100,10 @@ func (v *Muxer) SetRewriteHostFunc(f hostRewriteFunc) *Muxer {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Muxer) Close() error {
|
||||||
|
return v.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
type ChooseEndpointFunc func() (string, error)
|
type ChooseEndpointFunc func() (string, error)
|
||||||
|
|
||||||
type CreateConnFunc func(remoteAddr string) (net.Conn, error)
|
type CreateConnFunc func(remoteAddr string) (net.Conn, error)
|
||||||
|
@ -94,22 +94,22 @@ func (l *Logger) Spawn() *Logger {
|
|||||||
return nl
|
return nl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Logger) Errorf(format string, v ...interface{}) {
|
func (l *Logger) Errorf(format string, v ...any) {
|
||||||
log.Logger.Errorf(l.prefixString+format, v...)
|
log.Logger.Errorf(l.prefixString+format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Logger) Warnf(format string, v ...interface{}) {
|
func (l *Logger) Warnf(format string, v ...any) {
|
||||||
log.Logger.Warnf(l.prefixString+format, v...)
|
log.Logger.Warnf(l.prefixString+format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Logger) Infof(format string, v ...interface{}) {
|
func (l *Logger) Infof(format string, v ...any) {
|
||||||
log.Logger.Infof(l.prefixString+format, v...)
|
log.Logger.Infof(l.prefixString+format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Logger) Debugf(format string, v ...interface{}) {
|
func (l *Logger) Debugf(format string, v ...any) {
|
||||||
log.Logger.Debugf(l.prefixString+format, v...)
|
log.Logger.Debugf(l.prefixString+format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Logger) Tracef(format string, v ...interface{}) {
|
func (l *Logger) Tracef(format string, v ...any) {
|
||||||
log.Logger.Tracef(l.prefixString+format, v...)
|
log.Logger.Tracef(l.prefixString+format, v...)
|
||||||
}
|
}
|
||||||
|
@ -59,3 +59,13 @@ type ResourceController struct {
|
|||||||
// All server manager plugin
|
// All server manager plugin
|
||||||
PluginManager *plugin.Manager
|
PluginManager *plugin.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rc *ResourceController) Close() error {
|
||||||
|
if rc.VhostHTTPSMuxer != nil {
|
||||||
|
rc.VhostHTTPSMuxer.Close()
|
||||||
|
}
|
||||||
|
if rc.TCPMuxHTTPConnectMuxer != nil {
|
||||||
|
rc.TCPMuxHTTPConnectMuxer.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -197,7 +197,7 @@ func getConfByType(proxyType string) any {
|
|||||||
// Get proxy info.
|
// Get proxy info.
|
||||||
type ProxyStatsInfo struct {
|
type ProxyStatsInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Conf interface{} `json:"conf"`
|
Conf any `json:"conf"`
|
||||||
ClientVersion string `json:"clientVersion,omitempty"`
|
ClientVersion string `json:"clientVersion,omitempty"`
|
||||||
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
||||||
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
||||||
@ -273,7 +273,7 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt
|
|||||||
// Get proxy info by name.
|
// Get proxy info by name.
|
||||||
type GetProxyStatsResp struct {
|
type GetProxyStatsResp struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Conf interface{} `json:"conf"`
|
Conf any `json:"conf"`
|
||||||
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
||||||
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
||||||
CurConns int64 `json:"curConns"`
|
CurConns int64 `json:"curConns"`
|
||||||
|
@ -17,8 +17,7 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sync"
|
||||||
"github.com/fatedier/golib/errors"
|
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
@ -33,6 +32,7 @@ type XTCPProxy struct {
|
|||||||
cfg *v1.XTCPProxyConfig
|
cfg *v1.XTCPProxyConfig
|
||||||
|
|
||||||
closeCh chan struct{}
|
closeCh chan struct{}
|
||||||
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXTCPProxy(baseProxy *BaseProxy) Proxy {
|
func NewXTCPProxy(baseProxy *BaseProxy) Proxy {
|
||||||
@ -43,6 +43,7 @@ func NewXTCPProxy(baseProxy *BaseProxy) Proxy {
|
|||||||
return &XTCPProxy{
|
return &XTCPProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: baseProxy,
|
||||||
cfg: unwrapped,
|
cfg: unwrapped,
|
||||||
|
closeCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,9 +88,9 @@ func (pxy *XTCPProxy) Run() (remoteAddr string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *XTCPProxy) Close() {
|
func (pxy *XTCPProxy) Close() {
|
||||||
|
pxy.closeOnce.Do(func() {
|
||||||
pxy.BaseProxy.Close()
|
pxy.BaseProxy.Close()
|
||||||
pxy.rc.NatHoleController.CloseClient(pxy.GetName())
|
pxy.rc.NatHoleController.CloseClient(pxy.GetName())
|
||||||
_ = errors.PanicToError(func() {
|
|
||||||
close(pxy.closeCh)
|
close(pxy.closeCh)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -386,24 +386,30 @@ func (svr *Service) Run(ctx context.Context) {
|
|||||||
func (svr *Service) Close() error {
|
func (svr *Service) Close() error {
|
||||||
if svr.kcpListener != nil {
|
if svr.kcpListener != nil {
|
||||||
svr.kcpListener.Close()
|
svr.kcpListener.Close()
|
||||||
svr.kcpListener = nil
|
|
||||||
}
|
}
|
||||||
if svr.quicListener != nil {
|
if svr.quicListener != nil {
|
||||||
svr.quicListener.Close()
|
svr.quicListener.Close()
|
||||||
svr.quicListener = nil
|
|
||||||
}
|
}
|
||||||
if svr.websocketListener != nil {
|
if svr.websocketListener != nil {
|
||||||
svr.websocketListener.Close()
|
svr.websocketListener.Close()
|
||||||
svr.websocketListener = nil
|
|
||||||
}
|
}
|
||||||
if svr.tlsListener != nil {
|
if svr.tlsListener != nil {
|
||||||
svr.tlsListener.Close()
|
svr.tlsListener.Close()
|
||||||
svr.tlsConfig = nil
|
}
|
||||||
|
if svr.sshTunnelListener != nil {
|
||||||
|
svr.sshTunnelListener.Close()
|
||||||
}
|
}
|
||||||
if svr.listener != nil {
|
if svr.listener != nil {
|
||||||
svr.listener.Close()
|
svr.listener.Close()
|
||||||
svr.listener = nil
|
|
||||||
}
|
}
|
||||||
|
if svr.webServer != nil {
|
||||||
|
svr.webServer.Close()
|
||||||
|
}
|
||||||
|
if svr.sshTunnelGateway != nil {
|
||||||
|
svr.sshTunnelGateway.Close()
|
||||||
|
}
|
||||||
|
svr.rc.Close()
|
||||||
|
svr.muxer.Close()
|
||||||
svr.ctlManager.Close()
|
svr.ctlManager.Close()
|
||||||
if svr.cancel != nil {
|
if svr.cancel != nil {
|
||||||
svr.cancel()
|
svr.cancel()
|
||||||
|
@ -5,75 +5,75 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ExpectEqual expects the specified two are the same, otherwise an exception raises
|
// ExpectEqual expects the specified two are the same, otherwise an exception raises
|
||||||
func ExpectEqual(actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectEqual(actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.Equal(extra), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.Equal(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectEqualValues expects the specified two are the same, it not strict about type
|
// ExpectEqualValues expects the specified two are the same, it not strict about type
|
||||||
func ExpectEqualValues(actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectEqualValues(actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.BeEquivalentTo(extra), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.BeEquivalentTo(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectEqualValuesWithOffset(offset int, actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectEqualValuesWithOffset(offset int, actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1+offset, actual).To(gomega.BeEquivalentTo(extra), explain...)
|
gomega.ExpectWithOffset(1+offset, actual).To(gomega.BeEquivalentTo(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectNotEqual expects the specified two are not the same, otherwise an exception raises
|
// ExpectNotEqual expects the specified two are not the same, otherwise an exception raises
|
||||||
func ExpectNotEqual(actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectNotEqual(actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).NotTo(gomega.Equal(extra), explain...)
|
gomega.ExpectWithOffset(1, actual).NotTo(gomega.Equal(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectError expects an error happens, otherwise an exception raises
|
// ExpectError expects an error happens, otherwise an exception raises
|
||||||
func ExpectError(err error, explain ...interface{}) {
|
func ExpectError(err error, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred(), explain...)
|
gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred(), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectErrorWithOffset(offset int, err error, explain ...interface{}) {
|
func ExpectErrorWithOffset(offset int, err error, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1+offset, err).To(gomega.HaveOccurred(), explain...)
|
gomega.ExpectWithOffset(1+offset, err).To(gomega.HaveOccurred(), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectNoError checks if "err" is set, and if so, fails assertion while logging the error.
|
// ExpectNoError checks if "err" is set, and if so, fails assertion while logging the error.
|
||||||
func ExpectNoError(err error, explain ...interface{}) {
|
func ExpectNoError(err error, explain ...any) {
|
||||||
ExpectNoErrorWithOffset(1, err, explain...)
|
ExpectNoErrorWithOffset(1, err, explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectNoErrorWithOffset checks if "err" is set, and if so, fails assertion while logging the error at "offset" levels above its caller
|
// ExpectNoErrorWithOffset checks if "err" is set, and if so, fails assertion while logging the error at "offset" levels above its caller
|
||||||
// (for example, for call chain f -> g -> ExpectNoErrorWithOffset(1, ...) error would be logged for "f").
|
// (for example, for call chain f -> g -> ExpectNoErrorWithOffset(1, ...) error would be logged for "f").
|
||||||
func ExpectNoErrorWithOffset(offset int, err error, explain ...interface{}) {
|
func ExpectNoErrorWithOffset(offset int, err error, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1+offset, err).NotTo(gomega.HaveOccurred(), explain...)
|
gomega.ExpectWithOffset(1+offset, err).NotTo(gomega.HaveOccurred(), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectContainSubstring(actual, substr string, explain ...interface{}) {
|
func ExpectContainSubstring(actual, substr string, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.ContainSubstring(substr), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.ContainSubstring(substr), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectConsistOf expects actual contains precisely the extra elements. The ordering of the elements does not matter.
|
// ExpectConsistOf expects actual contains precisely the extra elements. The ordering of the elements does not matter.
|
||||||
func ExpectConsistOf(actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectConsistOf(actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.ConsistOf(extra), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.ConsistOf(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectContainElements(actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectContainElements(actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.ContainElements(extra), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.ContainElements(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectNotContainElements(actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectNotContainElements(actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).NotTo(gomega.ContainElements(extra), explain...)
|
gomega.ExpectWithOffset(1, actual).NotTo(gomega.ContainElements(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectHaveKey expects the actual map has the key in the keyset
|
// ExpectHaveKey expects the actual map has the key in the keyset
|
||||||
func ExpectHaveKey(actual interface{}, key interface{}, explain ...interface{}) {
|
func ExpectHaveKey(actual any, key any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.HaveKey(key), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.HaveKey(key), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectEmpty expects actual is empty
|
// ExpectEmpty expects actual is empty
|
||||||
func ExpectEmpty(actual interface{}, explain ...interface{}) {
|
func ExpectEmpty(actual any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.BeEmpty(), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.BeEmpty(), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectTrue(actual interface{}, explain ...interface{}) {
|
func ExpectTrue(actual any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).Should(gomega.BeTrue(), explain...)
|
gomega.ExpectWithOffset(1, actual).Should(gomega.BeTrue(), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectTrueWithOffset(offset int, actual interface{}, explain ...interface{}) {
|
func ExpectTrueWithOffset(offset int, actual any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1+offset, actual).Should(gomega.BeTrue(), explain...)
|
gomega.ExpectWithOffset(1+offset, actual).Should(gomega.BeTrue(), explain...)
|
||||||
}
|
}
|
||||||
|
@ -11,18 +11,18 @@ func nowStamp() string {
|
|||||||
return time.Now().Format(time.StampMilli)
|
return time.Now().Format(time.StampMilli)
|
||||||
}
|
}
|
||||||
|
|
||||||
func log(level string, format string, args ...interface{}) {
|
func log(level string, format string, args ...any) {
|
||||||
fmt.Fprintf(ginkgo.GinkgoWriter, nowStamp()+": "+level+": "+format+"\n", args...)
|
fmt.Fprintf(ginkgo.GinkgoWriter, nowStamp()+": "+level+": "+format+"\n", args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logf logs the info.
|
// Logf logs the info.
|
||||||
func Logf(format string, args ...interface{}) {
|
func Logf(format string, args ...any) {
|
||||||
log("INFO", format, args...)
|
log("INFO", format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Failf logs the fail info, including a stack trace starts with its direct caller
|
// Failf logs the fail info, including a stack trace starts with its direct caller
|
||||||
// (for example, for call chain f -> g -> Failf("foo", ...) error would be logged for "g").
|
// (for example, for call chain f -> g -> Failf("foo", ...) error would be logged for "g").
|
||||||
func Failf(format string, args ...interface{}) {
|
func Failf(format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
skip := 1
|
skip := 1
|
||||||
ginkgo.Fail(msg, skip)
|
ginkgo.Fail(msg, skip)
|
||||||
|
@ -67,8 +67,8 @@ func (m *MockServers) Close() {
|
|||||||
os.Remove(m.udsEchoServer.BindAddr())
|
os.Remove(m.udsEchoServer.BindAddr())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockServers) GetTemplateParams() map[string]interface{} {
|
func (m *MockServers) GetTemplateParams() map[string]any {
|
||||||
ret := make(map[string]interface{})
|
ret := make(map[string]any)
|
||||||
ret[TCPEchoServerPort] = m.tcpEchoServer.BindPort()
|
ret[TCPEchoServerPort] = m.tcpEchoServer.BindPort()
|
||||||
ret[UDPEchoServerPort] = m.udpEchoServer.BindPort()
|
ret[UDPEchoServerPort] = m.udpEchoServer.BindPort()
|
||||||
ret[UDSEchoServerAddr] = m.udsEchoServer.BindAddr()
|
ret[UDSEchoServerAddr] = m.udsEchoServer.BindAddr()
|
||||||
@ -76,7 +76,7 @@ func (m *MockServers) GetTemplateParams() map[string]interface{} {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockServers) GetParam(key string) interface{} {
|
func (m *MockServers) GetParam(key string) any {
|
||||||
params := m.GetTemplateParams()
|
params := m.GetTemplateParams()
|
||||||
if v, ok := params[key]; ok {
|
if v, ok := params[key]; ok {
|
||||||
return v
|
return v
|
||||||
|
@ -42,7 +42,7 @@ type RequestExpect struct {
|
|||||||
f *Framework
|
f *Framework
|
||||||
expectResp []byte
|
expectResp []byte
|
||||||
expectError bool
|
expectError bool
|
||||||
explain []interface{}
|
explain []any
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRequestExpect(f *Framework) *RequestExpect {
|
func NewRequestExpect(f *Framework) *RequestExpect {
|
||||||
@ -51,7 +51,7 @@ func NewRequestExpect(f *Framework) *RequestExpect {
|
|||||||
f: f,
|
f: f,
|
||||||
expectResp: []byte(consts.TestString),
|
expectResp: []byte(consts.TestString),
|
||||||
expectError: false,
|
expectError: false,
|
||||||
explain: make([]interface{}, 0),
|
explain: make([]any, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ func (e *RequestExpect) ExpectError(expectErr bool) *RequestExpect {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RequestExpect) Explain(explain ...interface{}) *RequestExpect {
|
func (e *RequestExpect) Explain(explain ...any) *RequestExpect {
|
||||||
e.explain = explain
|
e.explain = explain
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
type="textarea"
|
type="textarea"
|
||||||
autosize
|
autosize
|
||||||
v-model="textarea"
|
v-model="textarea"
|
||||||
placeholder="frpc configrue file, can not be empty..."
|
placeholder="frpc configure file, can not be empty..."
|
||||||
></el-input>
|
></el-input>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user