mirror of
https://gitee.com/IrisVega/frp.git
synced 2024-11-01 22:31:29 +08:00
proxy supports configuring annotations, which will be displayed in the frps dashboard (#4000)
This commit is contained in:
parent
9152c59570
commit
3529158f31
@ -526,6 +526,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 +536,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
|
||||||
|
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"
|
||||||
|
3
go.mod
3
go.mod
@ -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=
|
||||||
|
@ -107,6 +107,7 @@ 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"`
|
||||||
|
Annotations map[string]string `json:"annotations,omitempty"`
|
||||||
Transport ProxyTransport `json:"transport,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"`
|
||||||
@ -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…
Reference in New Issue
Block a user