mirror of
https://github.com/fatedier/frp.git
synced 2025-05-14 19:28:26 +00:00
Compare commits
6 Commits
6ebaa67070
...
6d5e3d9f57
Author | SHA1 | Date | |
---|---|---|---|
|
6d5e3d9f57 | ||
|
dc34a68542 | ||
|
3529158f31 | ||
|
9152c59570 | ||
|
2af2cf7dbd | ||
|
971cf0cebf |
@ -2,7 +2,7 @@ version: 2
|
|||||||
jobs:
|
jobs:
|
||||||
go-version-latest:
|
go-version-latest:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.21-node
|
- image: cimg/go:1.22-node
|
||||||
resource_class: large
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@ -10,7 +10,7 @@ jobs:
|
|||||||
- run: make alltest
|
- run: make alltest
|
||||||
go-version-last:
|
go-version-last:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.20-node
|
- image: cimg/go:1.21-node
|
||||||
resource_class: large
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
12
.github/workflows/build-and-push-image.yml
vendored
12
.github/workflows/build-and-push-image.yml
vendored
@ -19,15 +19,15 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# environment
|
# environment
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
# get image tag name
|
# get image tag name
|
||||||
- name: Get Image Tag Name
|
- name: Get Image Tag Name
|
||||||
@ -38,13 +38,13 @@ jobs:
|
|||||||
echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
- name: Login to the GPR
|
- name: Login to the GPR
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@ -72,7 +72,7 @@ jobs:
|
|||||||
${{ env.TAG_FRPC_GPR }}
|
${{ env.TAG_FRPC_GPR }}
|
||||||
|
|
||||||
- name: Build and push frps
|
- name: Build and push frps
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./dockerfiles/Dockerfile-for-frps
|
file: ./dockerfiles/Dockerfile-for-frps
|
||||||
|
8
.github/workflows/golangci-lint.yml
vendored
8
.github/workflows/golangci-lint.yml
vendored
@ -14,12 +14,12 @@ jobs:
|
|||||||
name: lint
|
name: lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.22'
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v4
|
||||||
with:
|
with:
|
||||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||||
version: v1.55
|
version: v1.55
|
||||||
|
8
.github/workflows/goreleaser.yml
vendored
8
.github/workflows/goreleaser.yml
vendored
@ -8,21 +8,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.22'
|
||||||
|
|
||||||
- name: Make All
|
- name: Make All
|
||||||
run: |
|
run: |
|
||||||
./package.sh
|
./package.sh
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v4
|
uses: goreleaser/goreleaser-action@v5
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --clean --release-notes=./Release.md
|
args: release --clean --release-notes=./Release.md
|
||||||
|
12
README.md
12
README.md
@ -11,10 +11,6 @@
|
|||||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||||
</a>
|
</a>
|
||||||
<a> </a>
|
|
||||||
<a href="https://www.nango.dev?utm_source=github&utm_medium=oss-banner&utm_campaign=fatedier-frp" target="_blank">
|
|
||||||
<img width="400px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_nango.png">
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
<!--gold sponsors end-->
|
<!--gold sponsors end-->
|
||||||
|
|
||||||
@ -201,11 +197,11 @@ This example implements multiple SSH services exposed through the same port usin
|
|||||||
|
|
||||||
4. To access internal machine A using SSH ProxyCommand, assuming the username is "test":
|
4. To access internal machine A using SSH ProxyCommand, assuming the username is "test":
|
||||||
|
|
||||||
`ssh -o 'proxycommand socat - PROXY:x.x.x.x:machine-a.example.com:22,proxyport=5002' test@machine-a`
|
`ssh -o 'proxycommand socat - PROXY:x.x.x.x:%h:%p,proxyport=5002' test@machine-a.example.com`
|
||||||
|
|
||||||
5. To access internal machine B, the only difference is the domain name, assuming the username is "test":
|
5. To access internal machine B, the only difference is the domain name, assuming the username is "test":
|
||||||
|
|
||||||
`ssh -o 'proxycommand socat - PROXY:x.x.x.x:machine-b.example.com:22,proxyport=5002' test@machine-b`
|
`ssh -o 'proxycommand socat - PROXY:x.x.x.x:%h:%p,proxyport=5002' test@machine-b.example.com`
|
||||||
|
|
||||||
### Accessing Internal Web Services with Custom Domains in LAN
|
### Accessing Internal Web Services with Custom Domains in LAN
|
||||||
|
|
||||||
@ -526,6 +522,8 @@ Check frp's status and proxies' statistics information by Dashboard.
|
|||||||
Configure a port for dashboard to enable this feature:
|
Configure a port for dashboard to enable this feature:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
# The default value is 127.0.0.1. Change it to 0.0.0.0 when you want to access it from a public network.
|
||||||
|
webServer.addr = "0.0.0.0"
|
||||||
webServer.port = 7500
|
webServer.port = 7500
|
||||||
# dashboard's username and password are both optional
|
# dashboard's username and password are both optional
|
||||||
webServer.user = "admin"
|
webServer.user = "admin"
|
||||||
@ -534,8 +532,6 @@ webServer.password = "admin"
|
|||||||
|
|
||||||
Then visit `http://[serverAddr]:7500` to see the dashboard, with username and password both being `admin`.
|
Then visit `http://[serverAddr]:7500` to see the dashboard, with username and password both being `admin`.
|
||||||
|
|
||||||
Note that if you want your server to be accessed from public networks, then also add `webServer.addr = "0.0.0.0"` line. For security reasons (credits [#3709](https://github.com/fatedier/frp/issues/3709)), value `127.0.0.1` is used by default.
|
|
||||||
|
|
||||||
Additionally, you can use HTTPS port by using your domains wildcard or normal SSL certificate:
|
Additionally, you can use HTTPS port by using your domains wildcard or normal SSL certificate:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
@ -13,10 +13,6 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP
|
|||||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||||
</a>
|
</a>
|
||||||
<a> </a>
|
|
||||||
<a href="https://www.nango.dev?utm_source=github&utm_medium=oss-banner&utm_campaign=fatedier-frp" target="_blank">
|
|
||||||
<img width="400px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_nango.png">
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
<!--gold sponsors end-->
|
<!--gold sponsors end-->
|
||||||
|
|
||||||
|
10
Release.md
10
Release.md
@ -1,11 +1,3 @@
|
|||||||
### Deprecation Notices
|
|
||||||
|
|
||||||
* Using an underscore in a flag name is deprecated and has been replaced by a hyphen. The underscore format will remain compatible for some time, until it is completely removed in a future version. For example, `--remote_port` is replaced with `--remote-port`.
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* The `Refresh` and `ClearOfflineProxies` buttons have been added to the Dashboard of frps.
|
* Proxy supports configuring annotations, which will be displayed in the frps dashboard.
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
* The host/domain matching in the routing rules has been changed to be case-insensitive.
|
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
84
assets/frps/static/index-ky4re_ta.js
Normal file
84
assets/frps/static/index-ky4re_ta.js
Normal file
File diff suppressed because one or more lines are too long
1
assets/frps/static/index-rzPDshRD.css
Normal file
1
assets/frps/static/index-rzPDshRD.css
Normal file
File diff suppressed because one or more lines are too long
@ -4,8 +4,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>frps dashboard</title>
|
<title>frps dashboard</title>
|
||||||
<script type="module" crossorigin src="./index-1gecbKzv.js"></script>
|
<script type="module" crossorigin src="./index-ky4re_ta.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./index-Lf6B06jY.css">
|
<link rel="stylesheet" crossorigin href="./index-rzPDshRD.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -164,11 +164,16 @@ healthCheck.type = "tcp"
|
|||||||
healthCheck.timeoutSeconds = 3
|
healthCheck.timeoutSeconds = 3
|
||||||
# If continuous failed in 3 times, the proxy will be removed from frps
|
# If continuous failed in 3 times, the proxy will be removed from frps
|
||||||
healthCheck.maxFailed = 3
|
healthCheck.maxFailed = 3
|
||||||
# every 10 seconds will do a health check
|
# Every 10 seconds will do a health check
|
||||||
healthCheck.intervalSeconds = 10
|
healthCheck.intervalSeconds = 10
|
||||||
# additional meta info for each proxy
|
# Additional meta info for each proxy. It will be passed to the server-side plugin for use.
|
||||||
metadatas.var1 = "abc"
|
metadatas.var1 = "abc"
|
||||||
metadatas.var2 = "123"
|
metadatas.var2 = "123"
|
||||||
|
# You can add some extra information to the proxy through annotations.
|
||||||
|
# These annotations will be displayed on the frps dashboard.
|
||||||
|
[proxies.annotations]
|
||||||
|
key1 = "value1"
|
||||||
|
"prefix/key2" = "value2"
|
||||||
|
|
||||||
[[proxies]]
|
[[proxies]]
|
||||||
name = "ssh_random"
|
name = "ssh_random"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.21 AS building
|
FROM golang:1.22 AS building
|
||||||
|
|
||||||
COPY . /building
|
COPY . /building
|
||||||
WORKDIR /building
|
WORKDIR /building
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.21 AS building
|
FROM golang:1.22 AS building
|
||||||
|
|
||||||
COPY . /building
|
COPY . /building
|
||||||
WORKDIR /building
|
WORKDIR /building
|
||||||
|
5
go.mod
5
go.mod
@ -1,6 +1,6 @@
|
|||||||
module github.com/fatedier/frp
|
module github.com/fatedier/frp
|
||||||
|
|
||||||
go 1.20
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||||
@ -24,6 +24,7 @@ require (
|
|||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
|
github.com/tidwall/gjson v1.17.1
|
||||||
golang.org/x/crypto v0.17.0
|
golang.org/x/crypto v0.17.0
|
||||||
golang.org/x/net v0.17.0
|
golang.org/x/net v0.17.0
|
||||||
golang.org/x/oauth2 v0.10.0
|
golang.org/x/oauth2 v0.10.0
|
||||||
@ -64,6 +65,8 @@ require (
|
|||||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||||
golang.org/x/mod v0.10.0 // indirect
|
golang.org/x/mod v0.10.0 // indirect
|
||||||
|
6
go.sum
6
go.sum
@ -145,6 +145,12 @@ github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7S
|
|||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
|
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||||
|
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||||
|
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
||||||
|
@ -175,6 +175,9 @@ func transformHeadersFromPluginParams(params map[string]string) v1.HeaderOperati
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
||||||
|
if out.Set == nil {
|
||||||
|
out.Set = make(map[string]string)
|
||||||
|
}
|
||||||
out.Set[k] = v
|
out.Set[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,9 +105,10 @@ type DomainConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProxyBaseConfig struct {
|
type ProxyBaseConfig struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Transport ProxyTransport `json:"transport,omitempty"`
|
Annotations map[string]string `json:"annotations,omitempty"`
|
||||||
|
Transport ProxyTransport `json:"transport,omitempty"`
|
||||||
// metadata info for each proxy
|
// metadata info for each proxy
|
||||||
Metadatas map[string]string `json:"metadatas,omitempty"`
|
Metadatas map[string]string `json:"metadatas,omitempty"`
|
||||||
LoadBalancer LoadBalancerConfig `json:"loadBalancer,omitempty"`
|
LoadBalancer LoadBalancerConfig `json:"loadBalancer,omitempty"`
|
||||||
@ -138,6 +139,7 @@ func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) {
|
|||||||
m.Group = c.LoadBalancer.Group
|
m.Group = c.LoadBalancer.Group
|
||||||
m.GroupKey = c.LoadBalancer.GroupKey
|
m.GroupKey = c.LoadBalancer.GroupKey
|
||||||
m.Metas = c.Metadatas
|
m.Metas = c.Metadatas
|
||||||
|
m.Annotations = c.Annotations
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ProxyBaseConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
func (c *ProxyBaseConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||||
@ -154,6 +156,7 @@ func (c *ProxyBaseConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
|||||||
c.LoadBalancer.Group = m.Group
|
c.LoadBalancer.Group = m.Group
|
||||||
c.LoadBalancer.GroupKey = m.GroupKey
|
c.LoadBalancer.GroupKey = m.GroupKey
|
||||||
c.Metadatas = m.Metas
|
c.Metadatas = m.Metas
|
||||||
|
c.Annotations = m.Annotations
|
||||||
}
|
}
|
||||||
|
|
||||||
type TypedProxyConfig struct {
|
type TypedProxyConfig struct {
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
)
|
)
|
||||||
@ -29,10 +30,12 @@ func validateProxyBaseConfigForClient(c *v1.ProxyBaseConfig) error {
|
|||||||
return errors.New("name should not be empty")
|
return errors.New("name should not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := ValidateAnnotations(c.Annotations); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if !lo.Contains([]string{"", "v1", "v2"}, c.Transport.ProxyProtocolVersion) {
|
if !lo.Contains([]string{"", "v1", "v2"}, c.Transport.ProxyProtocolVersion) {
|
||||||
return fmt.Errorf("not support proxy protocol version: %s", c.Transport.ProxyProtocolVersion)
|
return fmt.Errorf("not support proxy protocol version: %s", c.Transport.ProxyProtocolVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lo.Contains([]string{"client", "server"}, c.Transport.BandwidthLimitMode) {
|
if !lo.Contains([]string{"client", "server"}, c.Transport.BandwidthLimitMode) {
|
||||||
return fmt.Errorf("bandwidth limit mode should be client or server")
|
return fmt.Errorf("bandwidth limit mode should be client or server")
|
||||||
}
|
}
|
||||||
@ -61,7 +64,10 @@ func validateProxyBaseConfigForClient(c *v1.ProxyBaseConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateProxyBaseConfigForServer(c *v1.ProxyBaseConfig, s *v1.ServerConfig) error {
|
func validateProxyBaseConfigForServer(c *v1.ProxyBaseConfig) error {
|
||||||
|
if err := ValidateAnnotations(c.Annotations); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +167,7 @@ func validateSUDPProxyConfigForClient(c *v1.SUDPProxyConfig) error {
|
|||||||
|
|
||||||
func ValidateProxyConfigurerForServer(c v1.ProxyConfigurer, s *v1.ServerConfig) error {
|
func ValidateProxyConfigurerForServer(c v1.ProxyConfigurer, s *v1.ServerConfig) error {
|
||||||
base := c.GetBaseConfig()
|
base := c.GetBaseConfig()
|
||||||
if err := validateProxyBaseConfigForServer(base, s); err != nil {
|
if err := validateProxyBaseConfigForServer(base); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,3 +237,34 @@ func validateXTCPProxyConfigForServer(c *v1.XTCPProxyConfig, s *v1.ServerConfig)
|
|||||||
func validateSUDPProxyConfigForServer(c *v1.SUDPProxyConfig, s *v1.ServerConfig) error {
|
func validateSUDPProxyConfigForServer(c *v1.SUDPProxyConfig, s *v1.ServerConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateAnnotations validates that a set of annotations are correctly defined.
|
||||||
|
func ValidateAnnotations(annotations map[string]string) error {
|
||||||
|
if len(annotations) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs error
|
||||||
|
for k := range annotations {
|
||||||
|
for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) {
|
||||||
|
errs = AppendError(errs, fmt.Errorf("annotation key %s is invalid: %s", k, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ValidateAnnotationsSize(annotations); err != nil {
|
||||||
|
errs = AppendError(errs, err)
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
const TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
|
||||||
|
|
||||||
|
func ValidateAnnotationsSize(annotations map[string]string) error {
|
||||||
|
var totalSize int64
|
||||||
|
for k, v := range annotations {
|
||||||
|
totalSize += (int64)(len(k)) + (int64)(len(v))
|
||||||
|
}
|
||||||
|
if totalSize > (int64)(TotalAnnotationSizeLimitB) {
|
||||||
|
return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, TotalAnnotationSizeLimitB)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -108,6 +108,7 @@ type NewProxy struct {
|
|||||||
Group string `json:"group,omitempty"`
|
Group string `json:"group,omitempty"`
|
||||||
GroupKey string `json:"group_key,omitempty"`
|
GroupKey string `json:"group_key,omitempty"`
|
||||||
Metas map[string]string `json:"metas,omitempty"`
|
Metas map[string]string `json:"metas,omitempty"`
|
||||||
|
Annotations map[string]string `json:"annotations,omitempty"`
|
||||||
|
|
||||||
// tcp and udp only
|
// tcp and udp only
|
||||||
RemotePort int `json:"remote_port,omitempty"`
|
RemotePort int `json:"remote_port,omitempty"`
|
||||||
|
54
test/e2e/v1/basic/annotations.go
Normal file
54
test/e2e/v1/basic/annotations.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/onsi/ginkgo/v2"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/test/e2e/framework"
|
||||||
|
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = ginkgo.Describe("[Feature: Annotations]", func() {
|
||||||
|
f := framework.NewDefaultFramework()
|
||||||
|
|
||||||
|
ginkgo.It("Set Proxy Annotations", func() {
|
||||||
|
webPort := f.AllocPort()
|
||||||
|
|
||||||
|
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||||
|
webServer.port = %d
|
||||||
|
`, webPort)
|
||||||
|
|
||||||
|
p1Port := f.AllocPort()
|
||||||
|
|
||||||
|
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||||
|
[[proxies]]
|
||||||
|
name = "p1"
|
||||||
|
type = "tcp"
|
||||||
|
localPort = {{ .%s }}
|
||||||
|
remotePort = %d
|
||||||
|
[proxies.annotations]
|
||||||
|
"frp.e2e.test/foo" = "value1"
|
||||||
|
"frp.e2e.test/bar" = "value2"
|
||||||
|
`, framework.TCPEchoServerPort, p1Port)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).Port(p1Port).Ensure()
|
||||||
|
|
||||||
|
// check annotations in frps
|
||||||
|
resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/api/proxy/tcp/%s", webPort, "p1"))
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
framework.ExpectEqual(resp.StatusCode, 200)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
content, err := io.ReadAll(resp.Body)
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
|
annotations := gjson.Get(string(content), "conf.annotations").Map()
|
||||||
|
framework.ExpectEqual("value1", annotations["frp.e2e.test/foo"].String())
|
||||||
|
framework.ExpectEqual("value2", annotations["frp.e2e.test/bar"].String())
|
||||||
|
})
|
||||||
|
})
|
4
web/frps/components.d.ts
vendored
4
web/frps/components.d.ts
vendored
@ -9,19 +9,21 @@ declare module 'vue' {
|
|||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
|
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||||
ElForm: typeof import('element-plus/es')['ElForm']
|
ElForm: typeof import('element-plus/es')['ElForm']
|
||||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
|
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
|
||||||
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
|
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
|
||||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||||
ElTable: typeof import('element-plus/es')['ElTable']
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||||
ElTag: typeof import('element-plus/es')['ElTag']
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
|
ElText: typeof import('element-plus/es')['ElText']
|
||||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
LongSpan: typeof import('./src/components/LongSpan.vue')['default']
|
LongSpan: typeof import('./src/components/LongSpan.vue')['default']
|
||||||
ProxiesHTTP: typeof import('./src/components/ProxiesHTTP.vue')['default']
|
ProxiesHTTP: typeof import('./src/components/ProxiesHTTP.vue')['default']
|
||||||
|
@ -30,27 +30,6 @@
|
|||||||
>
|
>
|
||||||
<el-table-column type="expand">
|
<el-table-column type="expand">
|
||||||
<template #default="props">
|
<template #default="props">
|
||||||
<el-popover
|
|
||||||
placement="right"
|
|
||||||
width="600"
|
|
||||||
style="margin-left: 0px"
|
|
||||||
trigger="click"
|
|
||||||
>
|
|
||||||
<template #default>
|
|
||||||
<Traffic :proxyName="props.row.name" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #reference>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
size="large"
|
|
||||||
:name="props.row.name"
|
|
||||||
style="margin-bottom: 10px"
|
|
||||||
>Traffic Statistics
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-popover>
|
|
||||||
|
|
||||||
<ProxyViewExpand :row="props.row" :proxyType="proxyType" />
|
<ProxyViewExpand :row="props.row" :proxyType="proxyType" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -82,8 +61,27 @@
|
|||||||
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
|
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="Operations">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:name="scope.row.name"
|
||||||
|
style="margin-bottom: 10px"
|
||||||
|
@click="dialogVisibleName = scope.row.name; dialogVisible = true"
|
||||||
|
>Traffic
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
destroy-on-close="true"
|
||||||
|
:title="dialogVisibleName"
|
||||||
|
width="700px">
|
||||||
|
<Traffic :proxyName="dialogVisibleName" />
|
||||||
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -92,6 +90,7 @@ import type { TableColumnCtx } from 'element-plus'
|
|||||||
import type { BaseProxy } from '../utils/proxy.js'
|
import type { BaseProxy } from '../utils/proxy.js'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import ProxyViewExpand from './ProxyViewExpand.vue'
|
import ProxyViewExpand from './ProxyViewExpand.vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
proxies: BaseProxy[]
|
proxies: BaseProxy[]
|
||||||
@ -100,6 +99,9 @@ defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits(['refresh'])
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogVisibleName = ref("")
|
||||||
|
|
||||||
const formatTrafficIn = (row: BaseProxy, _: TableColumnCtx<BaseProxy>) => {
|
const formatTrafficIn = (row: BaseProxy, _: TableColumnCtx<BaseProxy>) => {
|
||||||
return Humanize.fileSize(row.trafficIn)
|
return Humanize.fileSize(row.trafficIn)
|
||||||
}
|
}
|
||||||
|
@ -60,11 +60,56 @@
|
|||||||
<span>{{ row.lastCloseTime }}</span>
|
<span>{{ row.lastCloseTime }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
|
<div v-if="row.annotations && row.annotations.size > 0">
|
||||||
|
<el-divider />
|
||||||
|
<el-text class="title-text" size="large">Annotations</el-text>
|
||||||
|
<ul>
|
||||||
|
<li v-for="item in annotationsArray()">
|
||||||
|
<span class="annotation-key">{{ item.key }}</span>
|
||||||
|
<span>{{ item.value }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
|
||||||
|
const props = defineProps<{
|
||||||
row: any
|
row: any
|
||||||
proxyType: string
|
proxyType: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
// annotationsArray returns an array of key-value pairs from the annotations map.
|
||||||
|
const annotationsArray = (): Array<{ key: string; value: string }> => {
|
||||||
|
const array: Array<{ key: string; value: any }> = [];
|
||||||
|
if (props.row.annotations) {
|
||||||
|
props.row.annotations.forEach((value: any, key: string) => {
|
||||||
|
array.push({ key, value });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li {
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul .annotation-key {
|
||||||
|
width: 300px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
color: #99a9bf;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
class BaseProxy {
|
class BaseProxy {
|
||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
|
annotations: Map<string, string>
|
||||||
encryption: boolean
|
encryption: boolean
|
||||||
compression: boolean
|
compression: boolean
|
||||||
conns: number
|
conns: number
|
||||||
@ -21,6 +22,13 @@ class BaseProxy {
|
|||||||
constructor(proxyStats: any) {
|
constructor(proxyStats: any) {
|
||||||
this.name = proxyStats.name
|
this.name = proxyStats.name
|
||||||
this.type = ''
|
this.type = ''
|
||||||
|
this.annotations = new Map<string, string>()
|
||||||
|
if (proxyStats.conf?.annotations) {
|
||||||
|
for (const key in proxyStats.conf.annotations) {
|
||||||
|
this.annotations.set(key, proxyStats.conf.annotations[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.encryption = false
|
this.encryption = false
|
||||||
this.compression = false
|
this.compression = false
|
||||||
this.encryption =
|
this.encryption =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user