mirror of
https://github.com/fatedier/frp.git
synced 2025-06-07 17:28:21 +00:00
commit
b2ae433e18
25
.circleci/config.yml
Normal file
25
.circleci/config.yml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
version: 2
|
||||||
|
jobs:
|
||||||
|
test1:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.15-node
|
||||||
|
working_directory: /go/src/github.com/fatedier/frp
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: make
|
||||||
|
- run: make alltest
|
||||||
|
test2:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.14-node
|
||||||
|
working_directory: /go/src/github.com/fatedier/frp
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: make
|
||||||
|
- run: make alltest
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
build_and_test:
|
||||||
|
jobs:
|
||||||
|
- test1
|
||||||
|
- test2
|
2
.github/workflows/goreleaser.yml
vendored
2
.github/workflows/goreleaser.yml
vendored
@ -27,4 +27,4 @@ jobs:
|
|||||||
version: latest
|
version: latest
|
||||||
args: release --rm-dist --release-notes=./Release.md
|
args: release --rm-dist --release-notes=./Release.md
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GPR_TOKEN }}
|
||||||
|
12
.travis.yml
12
.travis.yml
@ -1,12 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.14.x
|
|
||||||
- 1.15.x
|
|
||||||
|
|
||||||
install:
|
|
||||||
- make
|
|
||||||
|
|
||||||
script:
|
|
||||||
- make alltest
|
|
@ -1,6 +1,6 @@
|
|||||||
# frp
|
# frp
|
||||||
|
|
||||||
[](https://travis-ci.org/fatedier/frp)
|
[](https://circleci.com/gh/fatedier/frp)
|
||||||
[](https://github.com/fatedier/frp/releases)
|
[](https://github.com/fatedier/frp/releases)
|
||||||
|
|
||||||
[README](README.md) | [中文文档](README_zh.md)
|
[README](README.md) | [中文文档](README_zh.md)
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
### New
|
### New
|
||||||
|
|
||||||
* Command line parameters support `enable_prometheus`.
|
* Server Plugin supports HTTPS.
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* Fix IPv6 address parse problem.
|
||||||
|
* HTTP type proxy can't handle websocket protocol due to error `Connection` header value.
|
||||||
|
File diff suppressed because one or more lines are too long
Binary file not shown.
1
assets/frps/static/css/app.808290ae.css
Normal file
1
assets/frps/static/css/app.808290ae.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
.el-form-item span{margin-left:15px}.demo-table-expand{font-size:0}.demo-table-expand label{width:90px;color:#99a9bf}.demo-table-expand .el-form-item{margin-right:0;margin-bottom:0;width:50%}body{background-color:#fafafa;margin:0;font-family:-apple-system,BlinkMacSystemFont,Helvetica Neue,sans-serif}header{width:100%;height:60px}.header-color{background:#58b7ff}#content{margin-top:20px;padding-right:40px}.brand{color:#fff;background-color:transparent;margin-left:20px;float:left;line-height:25px;font-size:25px;padding:15px 15px;height:30px;text-decoration:none}.source{border:1px solid #eaeefb;border-radius:4px;transition:.2s;padding:24px}.server_info{margin-left:40px;font-size:0}.server_info label{width:150px;color:#99a9bf}.server_info .el-form-item{margin-right:0;margin-bottom:0;width:100%}
|
1
assets/frps/static/css/chunk-vendors.84bb20f7.css
Normal file
1
assets/frps/static/css/chunk-vendors.84bb20f7.css
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/frps/static/fonts/element-icons.535877f5.woff
Normal file
BIN
assets/frps/static/fonts/element-icons.535877f5.woff
Normal file
Binary file not shown.
BIN
assets/frps/static/fonts/element-icons.732389de.ttf
Normal file
BIN
assets/frps/static/fonts/element-icons.732389de.ttf
Normal file
Binary file not shown.
@ -1 +1 @@
|
|||||||
<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?14bea8276eef86cc7c61"></script><script type="text/javascript" src="vendor.js?51925ec1a77936b64d61"></script></body> </html>
|
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>frps-dashboard</title><link href="css/app.808290ae.css" rel="preload" as="style"><link href="css/chunk-vendors.84bb20f7.css" rel="preload" as="style"><link href="js/app.bb942a48.js" rel="preload" as="script"><link href="js/chunk-vendors.4421b07d.js" rel="preload" as="script"><link href="css/chunk-vendors.84bb20f7.css" rel="stylesheet"><link href="css/app.808290ae.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but frps-dashboard doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.4421b07d.js"></script><script src="js/app.bb942a48.js"></script></body></html>
|
2
assets/frps/static/js/app.bb942a48.js
Normal file
2
assets/frps/static/js/app.bb942a48.js
Normal file
File diff suppressed because one or more lines are too long
1
assets/frps/static/js/app.bb942a48.js.map
Normal file
1
assets/frps/static/js/app.bb942a48.js.map
Normal file
File diff suppressed because one or more lines are too long
34
assets/frps/static/js/chunk-vendors.4421b07d.js
Normal file
34
assets/frps/static/js/chunk-vendors.4421b07d.js
Normal file
File diff suppressed because one or more lines are too long
1
assets/frps/static/js/chunk-vendors.4421b07d.js.map
Normal file
1
assets/frps/static/js/chunk-vendors.4421b07d.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"51925ec1a77936b64d61"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -17,10 +17,10 @@ package client
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -222,8 +222,10 @@ func (ctl *Control) connectServer() (conn net.Conn, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HTTPProxy, ctl.clientCfg.Protocol,
|
|
||||||
fmt.Sprintf("%s:%d", ctl.clientCfg.ServerAddr, ctl.clientCfg.ServerPort), tlsConfig)
|
address := net.JoinHostPort(ctl.clientCfg.ServerAddr, strconv.Itoa(ctl.clientCfg.ServerPort))
|
||||||
|
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HTTPProxy, ctl.clientCfg.Protocol, address, tlsConfig)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("start new connection to server error: %v", err)
|
xl.Warn("start new connection to server error: %v", err)
|
||||||
return
|
return
|
||||||
@ -295,7 +297,7 @@ func (ctl *Control) msgHandler() {
|
|||||||
}()
|
}()
|
||||||
defer ctl.msgHandlerShutdown.Done()
|
defer ctl.msgHandlerShutdown.Done()
|
||||||
|
|
||||||
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartBeatInterval) * time.Second)
|
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartbeatInterval) * time.Second)
|
||||||
defer hbSend.Stop()
|
defer hbSend.Stop()
|
||||||
hbCheck := time.NewTicker(time.Second)
|
hbCheck := time.NewTicker(time.Second)
|
||||||
defer hbCheck.Stop()
|
defer hbCheck.Stop()
|
||||||
@ -314,7 +316,7 @@ func (ctl *Control) msgHandler() {
|
|||||||
}
|
}
|
||||||
ctl.sendCh <- pingMsg
|
ctl.sendCh <- pingMsg
|
||||||
case <-hbCheck.C:
|
case <-hbCheck.C:
|
||||||
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartBeatTimeout)*time.Second {
|
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartbeatTimeout)*time.Second {
|
||||||
xl.Warn("heartbeat timeout")
|
xl.Warn("heartbeat timeout")
|
||||||
// let reader() stop
|
// let reader() stop
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -215,8 +216,9 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HTTPProxy, svr.cfg.Protocol,
|
|
||||||
fmt.Sprintf("%s:%d", svr.cfg.ServerAddr, svr.cfg.ServerPort), tlsConfig)
|
address := net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort))
|
||||||
|
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HTTPProxy, svr.cfg.Protocol, address, tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -157,17 +157,16 @@ func parseClientCommonCfgFromIni(content string) (config.ClientCommonConf, error
|
|||||||
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||||
cfg = config.GetDefaultClientConf()
|
cfg = config.GetDefaultClientConf()
|
||||||
|
|
||||||
strs := strings.Split(serverAddr, ":")
|
ipStr, portStr, err := net.SplitHostPort(serverAddr)
|
||||||
if len(strs) < 2 {
|
if err != nil {
|
||||||
err = fmt.Errorf("invalid server_addr")
|
err = fmt.Errorf("invalid server_addr: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strs[0] != "" {
|
|
||||||
cfg.ServerAddr = strs[0]
|
cfg.ServerAddr = ipStr
|
||||||
}
|
cfg.ServerPort, err = strconv.Atoi(portStr)
|
||||||
cfg.ServerPort, err = strconv.Atoi(strs[1])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("invalid server_addr")
|
err = fmt.Errorf("invalid server_addr: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +105,7 @@ var rootCmd = &cobra.Command{
|
|||||||
var cfg config.ServerCommonConf
|
var cfg config.ServerCommonConf
|
||||||
var err error
|
var err error
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
|
log.Info("frps uses config file: %s", cfgFile)
|
||||||
var content string
|
var content string
|
||||||
content, err = config.GetRenderedConfFromFile(cfgFile)
|
content, err = config.GetRenderedConfFromFile(cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -112,6 +113,7 @@ var rootCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
|
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
|
||||||
} else {
|
} else {
|
||||||
|
log.Info("frps uses command line arguments for config")
|
||||||
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -212,7 +214,7 @@ func runServer(cfg config.ServerCommonConf) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info("start frps success")
|
log.Info("frps started successfully")
|
||||||
svr.Run()
|
svr.Run()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -23,15 +23,30 @@ log_max_days = 3
|
|||||||
disable_log_color = false
|
disable_log_color = false
|
||||||
|
|
||||||
# for authentication, should be same as your frps.ini
|
# for authentication, should be same as your frps.ini
|
||||||
# AuthenticateHeartBeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false.
|
# authenticate_heartbeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false.
|
||||||
authenticate_heartbeats = false
|
authenticate_heartbeats = false
|
||||||
|
|
||||||
# AuthenticateNewWorkConns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false.
|
# authenticate_new_work_conns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false.
|
||||||
authenticate_new_work_conns = false
|
authenticate_new_work_conns = false
|
||||||
|
|
||||||
# auth token
|
# auth token
|
||||||
token = 12345678
|
token = 12345678
|
||||||
|
|
||||||
|
# oidc_client_id specifies the client ID to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
||||||
|
# By default, this value is "".
|
||||||
|
oidc_client_id =
|
||||||
|
|
||||||
|
# oidc_client_secret specifies the client secret to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
||||||
|
# By default, this value is "".
|
||||||
|
oidc_client_secret =
|
||||||
|
|
||||||
|
# oidc_audience specifies the audience of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
|
oidc_audience =
|
||||||
|
|
||||||
|
# oidc_token_endpoint_url specifies the URL which implements OIDC Token Endpoint.
|
||||||
|
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
|
oidc_token_endpoint_url =
|
||||||
|
|
||||||
# set admin address for control frpc's action by http api such as reload
|
# set admin address for control frpc's action by http api such as reload
|
||||||
admin_addr = 127.0.0.1
|
admin_addr = 127.0.0.1
|
||||||
admin_port = 7400
|
admin_port = 7400
|
||||||
|
@ -23,7 +23,7 @@ vhost_https_port = 443
|
|||||||
# response header timeout(seconds) for vhost http server, default is 60s
|
# response header timeout(seconds) for vhost http server, default is 60s
|
||||||
# vhost_http_timeout = 60
|
# vhost_http_timeout = 60
|
||||||
|
|
||||||
# TcpMuxHttpConnectPort specifies the port that the server listens for TCP
|
# tcpmux_httpconnect_port specifies the port that the server listens for TCP
|
||||||
# HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
# HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||||
# requests on one single port. If it's not - it will listen on this value for
|
# requests on one single port. If it's not - it will listen on this value for
|
||||||
# HTTP CONNECT requests. By default, this value is 0.
|
# HTTP CONNECT requests. By default, this value is 0.
|
||||||
@ -44,6 +44,7 @@ enable_prometheus = true
|
|||||||
|
|
||||||
# dashboard assets directory(only for debug mode)
|
# dashboard assets directory(only for debug mode)
|
||||||
# assets_dir = ./static
|
# assets_dir = ./static
|
||||||
|
|
||||||
# console or real logFile path like ./frps.log
|
# console or real logFile path like ./frps.log
|
||||||
log_file = ./frps.log
|
log_file = ./frps.log
|
||||||
|
|
||||||
@ -58,12 +59,12 @@ disable_log_color = false
|
|||||||
# DetailedErrorsToClient defines whether to send the specific error (with debug info) to frpc. By default, this value is true.
|
# DetailedErrorsToClient defines whether to send the specific error (with debug info) to frpc. By default, this value is true.
|
||||||
detailed_errors_to_client = true
|
detailed_errors_to_client = true
|
||||||
|
|
||||||
# AuthenticationMethod specifies what authentication method to use authenticate frpc with frps.
|
# authentication_method specifies what authentication method to use authenticate frpc with frps.
|
||||||
# If "token" is specified - token will be read into login message.
|
# If "token" is specified - token will be read into login message.
|
||||||
# If "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "token".
|
# If "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "token".
|
||||||
authentication_method = token
|
authentication_method = token
|
||||||
|
|
||||||
# AuthenticateHeartBeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false.
|
# authenticate_heartbeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false.
|
||||||
authenticate_heartbeats = false
|
authenticate_heartbeats = false
|
||||||
|
|
||||||
# AuthenticateNewWorkConns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false.
|
# AuthenticateNewWorkConns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false.
|
||||||
@ -72,25 +73,31 @@ authenticate_new_work_conns = false
|
|||||||
# auth token
|
# auth token
|
||||||
token = 12345678
|
token = 12345678
|
||||||
|
|
||||||
# OidcClientId specifies the client ID to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
# oidc_issuer specifies the issuer to verify OIDC tokens with.
|
||||||
# By default, this value is "".
|
# By default, this value is "".
|
||||||
oidc_client_id =
|
oidc_issuer =
|
||||||
|
|
||||||
# OidcClientSecret specifies the client secret to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
|
# oidc_audience specifies the audience OIDC tokens should contain when validated.
|
||||||
# By default, this value is "".
|
# By default, this value is "".
|
||||||
oidc_client_secret =
|
|
||||||
|
|
||||||
# OidcAudience specifies the audience of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
|
||||||
oidc_audience =
|
oidc_audience =
|
||||||
|
|
||||||
# OidcTokenEndpointUrl specifies the URL which implements OIDC Token Endpoint.
|
# oidc_skip_expiry_check specifies whether to skip checking if the OIDC token is expired.
|
||||||
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
|
# By default, this value is false.
|
||||||
oidc_token_endpoint_url =
|
oidc_skip_expiry_check = false
|
||||||
|
|
||||||
|
|
||||||
|
# oidc_skip_issuer_check specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer.
|
||||||
|
# By default, this value is false.
|
||||||
|
oidc_skip_issuer_check = false
|
||||||
|
|
||||||
# heartbeat configure, it's not recommended to modify the default value
|
# heartbeat configure, it's not recommended to modify the default value
|
||||||
# the default value of heartbeat_timeout is 90
|
# the default value of heartbeat_timeout is 90
|
||||||
# heartbeat_timeout = 90
|
# heartbeat_timeout = 90
|
||||||
|
|
||||||
|
# user_conn_timeout configure, it's not recommended to modify the default value
|
||||||
|
# the default value of user_conn_timeout is 10
|
||||||
|
# user_conn_timeout = 10
|
||||||
|
|
||||||
# only allow frpc to bind ports you list, if you set nothing, there won't be any limit
|
# only allow frpc to bind ports you list, if you set nothing, there won't be any limit
|
||||||
allow_ports = 2000-3000,3001,3003,4000-50000
|
allow_ports = 2000-3000,3001,3003,4000-50000
|
||||||
|
|
||||||
@ -100,7 +107,7 @@ max_pool_count = 5
|
|||||||
# max ports can be used for each client, default value is 0 means no limit
|
# max ports can be used for each client, default value is 0 means no limit
|
||||||
max_ports_per_client = 0
|
max_ports_per_client = 0
|
||||||
|
|
||||||
# TlsOnly specifies whether to only accept TLS-encrypted connections. By default, the value is false.
|
# tls_only specifies whether to only accept TLS-encrypted connections. By default, the value is false.
|
||||||
tls_only = false
|
tls_only = false
|
||||||
|
|
||||||
# tls_cert_file = server.crt
|
# tls_cert_file = server.crt
|
||||||
|
@ -209,9 +209,10 @@ path = /handler
|
|||||||
ops = NewProxy
|
ops = NewProxy
|
||||||
```
|
```
|
||||||
|
|
||||||
addr: the address where the external RPC service listens on.
|
- addr: the address where the external RPC service listens. Defaults to http. For https, specify the schema: `addr = https://127.0.0.1:9001`.
|
||||||
path: http request url path for the POST request.
|
- path: http request url path for the POST request.
|
||||||
ops: operations plugin needs to handle (e.g. "Login", "NewProxy", ...).
|
- ops: operations plugin needs to handle (e.g. "Login", "NewProxy", ...).
|
||||||
|
- tls_verify: When the schema is https, we verify by default. Set this value to false if you want to skip verification.
|
||||||
|
|
||||||
### Metadata
|
### Metadata
|
||||||
|
|
||||||
|
@ -121,11 +121,11 @@ type ClientCommonConf struct {
|
|||||||
// HeartBeatInterval specifies at what interval heartbeats are sent to the
|
// HeartBeatInterval specifies at what interval heartbeats are sent to the
|
||||||
// server, in seconds. It is not recommended to change this value. By
|
// server, in seconds. It is not recommended to change this value. By
|
||||||
// default, this value is 30.
|
// default, this value is 30.
|
||||||
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
HeartbeatInterval int64 `json:"heartbeat_interval"`
|
||||||
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
||||||
// before the connection is terminated, in seconds. It is not recommended
|
// before the connection is terminated, in seconds. It is not recommended
|
||||||
// to change this value. By default, this value is 90.
|
// to change this value. By default, this value is 90.
|
||||||
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
HeartbeatTimeout int64 `json:"heartbeat_timeout"`
|
||||||
// Client meta info
|
// Client meta info
|
||||||
Metas map[string]string `json:"metas"`
|
Metas map[string]string `json:"metas"`
|
||||||
// UDPPacketSize specifies the udp packet size
|
// UDPPacketSize specifies the udp packet size
|
||||||
@ -160,8 +160,8 @@ func GetDefaultClientConf() ClientCommonConf {
|
|||||||
TLSCertFile: "",
|
TLSCertFile: "",
|
||||||
TLSKeyFile: "",
|
TLSKeyFile: "",
|
||||||
TLSTrustedCaFile: "",
|
TLSTrustedCaFile: "",
|
||||||
HeartBeatInterval: 30,
|
HeartbeatInterval: 30,
|
||||||
HeartBeatTimeout: 90,
|
HeartbeatTimeout: 90,
|
||||||
Metas: make(map[string]string),
|
Metas: make(map[string]string),
|
||||||
UDPPacketSize: 1500,
|
UDPPacketSize: 1500,
|
||||||
}
|
}
|
||||||
@ -312,7 +312,7 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error
|
|||||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg.HeartBeatTimeout = v
|
cfg.HeartbeatTimeout = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "heartbeat_interval"); ok {
|
if tmpStr, ok = conf.Get("common", "heartbeat_interval"); ok {
|
||||||
@ -320,7 +320,7 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error
|
|||||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg.HeartBeatInterval = v
|
cfg.HeartbeatInterval = v
|
||||||
}
|
}
|
||||||
for k, v := range conf.Section("common") {
|
for k, v := range conf.Section("common") {
|
||||||
if strings.HasPrefix(k, "meta_") {
|
if strings.HasPrefix(k, "meta_") {
|
||||||
@ -338,12 +338,12 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *ClientCommonConf) Check() (err error) {
|
func (cfg *ClientCommonConf) Check() (err error) {
|
||||||
if cfg.HeartBeatInterval <= 0 {
|
if cfg.HeartbeatInterval <= 0 {
|
||||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.HeartBeatTimeout < cfg.HeartBeatInterval {
|
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
||||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ type ServerCommonConf struct {
|
|||||||
// AssetsDir specifies the local directory that the dashboard will load
|
// AssetsDir specifies the local directory that the dashboard will load
|
||||||
// resources from. If this value is "", assets will be loaded from the
|
// resources from. If this value is "", assets will be loaded from the
|
||||||
// bundled executable using statik. By default, this value is "".
|
// bundled executable using statik. By default, this value is "".
|
||||||
AssetsDir string `json:"asserts_dir"`
|
AssetsDir string `json:"assets_dir"`
|
||||||
// LogFile specifies a file where logs will be written to. This value will
|
// LogFile specifies a file where logs will be written to. This value will
|
||||||
// only be used if LogWay is set appropriately. By default, this value is
|
// only be used if LogWay is set appropriately. By default, this value is
|
||||||
// "console".
|
// "console".
|
||||||
@ -154,7 +154,7 @@ type ServerCommonConf struct {
|
|||||||
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
||||||
// before terminating the connection. It is not recommended to change this
|
// before terminating the connection. It is not recommended to change this
|
||||||
// value. By default, this value is 90.
|
// value. By default, this value is 90.
|
||||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
HeartbeatTimeout int64 `json:"heartbeat_timeout"`
|
||||||
// UserConnTimeout specifies the maximum time to wait for a work
|
// UserConnTimeout specifies the maximum time to wait for a work
|
||||||
// connection. By default, this value is 10.
|
// connection. By default, this value is 10.
|
||||||
UserConnTimeout int64 `json:"user_conn_timeout"`
|
UserConnTimeout int64 `json:"user_conn_timeout"`
|
||||||
@ -199,7 +199,7 @@ func GetDefaultServerConf() ServerCommonConf {
|
|||||||
TLSCertFile: "",
|
TLSCertFile: "",
|
||||||
TLSKeyFile: "",
|
TLSKeyFile: "",
|
||||||
TLSTrustedCaFile: "",
|
TLSTrustedCaFile: "",
|
||||||
HeartBeatTimeout: 90,
|
HeartbeatTimeout: 90,
|
||||||
UserConnTimeout: 10,
|
UserConnTimeout: 10,
|
||||||
Custom404Page: "",
|
Custom404Page: "",
|
||||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||||
@ -421,7 +421,7 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error
|
|||||||
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
|
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg.HeartBeatTimeout = v
|
cfg.HeartbeatTimeout = v
|
||||||
}
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "tls_only"); ok && tmpStr == "true" {
|
if tmpStr, ok = conf.Get("common", "tls_only"); ok && tmpStr == "true" {
|
||||||
@ -458,11 +458,16 @@ func UnmarshalPluginsFromIni(sections ini.File, cfg *ServerCommonConf) {
|
|||||||
for name, section := range sections {
|
for name, section := range sections {
|
||||||
if strings.HasPrefix(name, "plugin.") {
|
if strings.HasPrefix(name, "plugin.") {
|
||||||
name = strings.TrimSpace(strings.TrimPrefix(name, "plugin."))
|
name = strings.TrimSpace(strings.TrimPrefix(name, "plugin."))
|
||||||
|
var tls_verify, err = strconv.ParseBool(section["tls_verify"])
|
||||||
|
if err != nil {
|
||||||
|
tls_verify = true
|
||||||
|
}
|
||||||
options := plugin.HTTPPluginOptions{
|
options := plugin.HTTPPluginOptions{
|
||||||
Name: name,
|
Name: name,
|
||||||
Addr: section["addr"],
|
Addr: section["addr"],
|
||||||
Path: section["path"],
|
Path: section["path"],
|
||||||
Ops: strings.Split(section["ops"], ","),
|
Ops: strings.Split(section["ops"], ","),
|
||||||
|
TLSVerify: tls_verify,
|
||||||
}
|
}
|
||||||
for i := range options.Ops {
|
for i := range options.Ops {
|
||||||
options.Ops[i] = strings.TrimSpace(options.Ops[i])
|
options.Ops[i] = strings.TrimSpace(options.Ops[i])
|
||||||
|
@ -17,12 +17,14 @@ package plugin
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPPluginOptions struct {
|
type HTTPPluginOptions struct {
|
||||||
@ -30,6 +32,7 @@ type HTTPPluginOptions struct {
|
|||||||
Addr string
|
Addr string
|
||||||
Path string
|
Path string
|
||||||
Ops []string
|
Ops []string
|
||||||
|
TLSVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpPlugin struct {
|
type httpPlugin struct {
|
||||||
@ -40,10 +43,25 @@ type httpPlugin struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPPluginOptions(options HTTPPluginOptions) Plugin {
|
func NewHTTPPluginOptions(options HTTPPluginOptions) Plugin {
|
||||||
|
var url = fmt.Sprintf("%s%s", options.Addr, options.Path)
|
||||||
|
|
||||||
|
var client *http.Client
|
||||||
|
if strings.HasPrefix(url, "https://") {
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: options.TLSVerify == false},
|
||||||
|
}
|
||||||
|
client = &http.Client{Transport: tr}
|
||||||
|
} else {
|
||||||
|
client = &http.Client{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") {
|
||||||
|
url = "http://" + url
|
||||||
|
}
|
||||||
return &httpPlugin{
|
return &httpPlugin{
|
||||||
options: options,
|
options: options,
|
||||||
url: fmt.Sprintf("http://%s%s", options.Addr, options.Path),
|
url: url,
|
||||||
client: &http.Client{},
|
client: client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -33,6 +34,7 @@ func OkResponse() *http.Response {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: use "CanonicalHost" func to replace all "GetHostFromAddr" func.
|
||||||
func GetHostFromAddr(addr string) (host string) {
|
func GetHostFromAddr(addr string) (host string) {
|
||||||
strs := strings.Split(addr, ":")
|
strs := strings.Split(addr, ":")
|
||||||
if len(strs) > 1 {
|
if len(strs) > 1 {
|
||||||
@ -42,3 +44,34 @@ func GetHostFromAddr(addr string) (host string) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// canonicalHost strips port from host if present and returns the canonicalized
|
||||||
|
// host name.
|
||||||
|
func CanonicalHost(host string) (string, error) {
|
||||||
|
var err error
|
||||||
|
host = strings.ToLower(host)
|
||||||
|
if hasPort(host) {
|
||||||
|
host, _, err = net.SplitHostPort(host)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(host, ".") {
|
||||||
|
// Strip trailing dot from fully qualified domain names.
|
||||||
|
host = host[:len(host)-1]
|
||||||
|
}
|
||||||
|
return host, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasPort reports whether host contains a port number. host may be a host
|
||||||
|
// name, an IPv4 or an IPv6 address.
|
||||||
|
func hasPort(host string) bool {
|
||||||
|
colons := strings.Count(host, ":")
|
||||||
|
if colons == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if colons == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return host[0] == '[' && strings.Contains(host, "]:")
|
||||||
|
}
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.34.3"
|
var version string = "0.35.0"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
@ -17,6 +17,7 @@ package vhost
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@ -59,20 +60,25 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
|||||||
req.URL.Scheme = "http"
|
req.URL.Scheme = "http"
|
||||||
url := req.Context().Value(RouteInfoURL).(string)
|
url := req.Context().Value(RouteInfoURL).(string)
|
||||||
oldHost := util.GetHostFromAddr(req.Context().Value(RouteInfoHost).(string))
|
oldHost := util.GetHostFromAddr(req.Context().Value(RouteInfoHost).(string))
|
||||||
host := rp.GetRealHost(oldHost, url)
|
rc := rp.GetRouteConfig(oldHost, url)
|
||||||
if host != "" {
|
if rc != nil {
|
||||||
req.Host = host
|
if rc.RewriteHost != "" {
|
||||||
|
req.Host = rc.RewriteHost
|
||||||
}
|
}
|
||||||
req.URL.Host = req.Host
|
// Set {domain}.{location} as URL host here to let http transport reuse connections.
|
||||||
|
req.URL.Host = rc.Domain + "." + base64.StdEncoding.EncodeToString([]byte(rc.Location))
|
||||||
|
|
||||||
headers := rp.GetHeaders(oldHost, url)
|
for k, v := range rc.Headers {
|
||||||
for k, v := range headers {
|
|
||||||
req.Header.Set(k, v)
|
req.Header.Set(k, v)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
req.URL.Host = req.Host
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
ResponseHeaderTimeout: rp.responseHeaderTimeout,
|
ResponseHeaderTimeout: rp.responseHeaderTimeout,
|
||||||
DisableKeepAlives: true,
|
IdleConnTimeout: 60 * time.Second,
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
url := ctx.Value(RouteInfoURL).(string)
|
url := ctx.Value(RouteInfoURL).(string)
|
||||||
host := util.GetHostFromAddr(ctx.Value(RouteInfoHost).(string))
|
host := util.GetHostFromAddr(ctx.Value(RouteInfoHost).(string))
|
||||||
@ -107,6 +113,14 @@ func (rp *HTTPReverseProxy) UnRegister(domain string, location string) {
|
|||||||
rp.vhostRouter.Del(domain, location)
|
rp.vhostRouter.Del(domain, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rp *HTTPReverseProxy) GetRouteConfig(domain string, location string) *RouteConfig {
|
||||||
|
vr, ok := rp.getVhost(domain, location)
|
||||||
|
if ok {
|
||||||
|
return vr.payload.(*RouteConfig)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (rp *HTTPReverseProxy) GetRealHost(domain string, location string) (host string) {
|
func (rp *HTTPReverseProxy) GetRealHost(domain string, location string) (host string) {
|
||||||
vr, ok := rp.getVhost(domain, location)
|
vr, ok := rp.getVhost(domain, location)
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -144,7 +144,7 @@ func (v *Muxer) handle(c net.Conn) {
|
|||||||
|
|
||||||
sConn, reqInfoMap, err := v.vhostFunc(c)
|
sConn, reqInfoMap, err := v.vhostFunc(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("get hostname from http/https request error: %v", err)
|
log.Debug("get hostname from http/https request error: %v", err)
|
||||||
c.Close()
|
c.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -408,7 +408,7 @@ func (ctl *Control) manager() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-heartbeat.C:
|
case <-heartbeat.C:
|
||||||
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartBeatTimeout)*time.Second {
|
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartbeatTimeout)*time.Second {
|
||||||
xl.Warn("heartbeat timeout")
|
xl.Warn("heartbeat timeout")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
SubdomainHost: svr.cfg.SubDomainHost,
|
SubdomainHost: svr.cfg.SubDomainHost,
|
||||||
MaxPoolCount: svr.cfg.MaxPoolCount,
|
MaxPoolCount: svr.cfg.MaxPoolCount,
|
||||||
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
|
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
|
||||||
HeartBeatTimeout: svr.cfg.HeartBeatTimeout,
|
HeartBeatTimeout: svr.cfg.HeartbeatTimeout,
|
||||||
|
|
||||||
TotalTrafficIn: serverStats.TotalTrafficIn,
|
TotalTrafficIn: serverStats.TotalTrafficIn,
|
||||||
TotalTrafficOut: serverStats.TotalTrafficOut,
|
TotalTrafficOut: serverStats.TotalTrafficOut,
|
||||||
|
@ -100,7 +100,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn,
|
|||||||
xl.Warn("failed to get work connection: %v", err)
|
xl.Warn("failed to get work connection: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
xl.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
xl.Debug("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
||||||
xl.Spawn().AppendPrefix(pxy.GetName())
|
xl.Spawn().AppendPrefix(pxy.GetName())
|
||||||
workConn = frpNet.NewContextConn(pxy.ctx, workConn)
|
workConn = frpNet.NewContextConn(pxy.ctx, workConn)
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, net.Conn,
|
|||||||
xl.Info("listener is closed")
|
xl.Info("listener is closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
xl.Debug("get a user connection [%s]", c.RemoteAddr().String())
|
xl.Info("get a user connection [%s]", c.RemoteAddr().String())
|
||||||
go handler(p, c, pxy.serverCfg)
|
go handler(p, c, pxy.serverCfg)
|
||||||
}
|
}
|
||||||
}(listener)
|
}(listener)
|
||||||
|
@ -139,6 +139,7 @@ func TestHealthCheck(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
httpSvc3 := mock.NewHTTPServer(15005, func(w http.ResponseWriter, r *http.Request) {
|
httpSvc3 := mock.NewHTTPServer(15005, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
time.Sleep(time.Second)
|
||||||
w.Write([]byte("http3"))
|
w.Write([]byte("http3"))
|
||||||
})
|
})
|
||||||
err = httpSvc3.Start()
|
err = httpSvc3.Start()
|
||||||
@ -147,6 +148,7 @@ func TestHealthCheck(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
httpSvc4 := mock.NewHTTPServer(15006, func(w http.ResponseWriter, r *http.Request) {
|
httpSvc4 := mock.NewHTTPServer(15006, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
time.Sleep(time.Second)
|
||||||
w.Write([]byte("http4"))
|
w.Write([]byte("http4"))
|
||||||
})
|
})
|
||||||
err = httpSvc4.Start()
|
err = httpSvc4.Start()
|
||||||
@ -277,16 +279,30 @@ func TestHealthCheck(t *testing.T) {
|
|||||||
|
|
||||||
// ****** load balancing type http ******
|
// ****** load balancing type http ******
|
||||||
result = make([]string, 0)
|
result = make([]string, 0)
|
||||||
|
var wait sync.WaitGroup
|
||||||
|
var mu sync.Mutex
|
||||||
|
wait.Add(2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wait.Done()
|
||||||
|
code, body, _, err := util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(200, code)
|
||||||
|
mu.Lock()
|
||||||
|
result = append(result, body)
|
||||||
|
mu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wait.Done()
|
||||||
code, body, _, err = util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
|
code, body, _, err = util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal(200, code)
|
assert.Equal(200, code)
|
||||||
|
mu.Lock()
|
||||||
result = append(result, body)
|
result = append(result, body)
|
||||||
|
mu.Unlock()
|
||||||
code, body, _, err = util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
|
}()
|
||||||
assert.NoError(err)
|
wait.Wait()
|
||||||
assert.Equal(200, code)
|
|
||||||
result = append(result, body)
|
|
||||||
|
|
||||||
assert.Contains(result, "http3")
|
assert.Contains(result, "http3")
|
||||||
assert.Contains(result, "http4")
|
assert.Contains(result, "http4")
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
["es2015", { "modules": false }]
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
[
|
|
||||||
"component",
|
|
||||||
{
|
|
||||||
"libraryName": "element-ui",
|
|
||||||
"styleLibraryName": "theme-chalk"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
5
web/frps/.editorconfig
Normal file
5
web/frps/.editorconfig
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[*.{js,jsx,ts,tsx,vue}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
2
web/frps/.env.development
Normal file
2
web/frps/.env.development
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# just a flag
|
||||||
|
ENV = 'development'
|
2
web/frps/.env.production
Normal file
2
web/frps/.env.production
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# just a flag
|
||||||
|
ENV = 'production'
|
267
web/frps/.eslintrc.js
Normal file
267
web/frps/.eslintrc.js
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parserOptions: {
|
||||||
|
parser: 'babel-eslint',
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
es6: true
|
||||||
|
},
|
||||||
|
extends: ['plugin:vue/recommended', 'eslint:recommended'],
|
||||||
|
|
||||||
|
// add your custom rules here
|
||||||
|
// it is base on https://github.com/vuejs/eslint-config-vue
|
||||||
|
rules: {
|
||||||
|
'vue/max-attributes-per-line': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
singleline: 10,
|
||||||
|
multiline: {
|
||||||
|
max: 1,
|
||||||
|
allowFirstLine: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'vue/singleline-html-element-content-newline': 'off',
|
||||||
|
'vue/multiline-html-element-content-newline': 'off',
|
||||||
|
'vue/name-property-casing': ['error', 'PascalCase'],
|
||||||
|
'vue/no-v-html': 'off',
|
||||||
|
'accessor-pairs': 2,
|
||||||
|
'arrow-spacing': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
before: true,
|
||||||
|
after: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'block-spacing': [2, 'always'],
|
||||||
|
'brace-style': [
|
||||||
|
2,
|
||||||
|
'1tbs',
|
||||||
|
{
|
||||||
|
allowSingleLine: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
camelcase: [
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
properties: 'always'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'comma-dangle': [2, 'never'],
|
||||||
|
'comma-spacing': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
before: false,
|
||||||
|
after: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'comma-style': [2, 'last'],
|
||||||
|
'constructor-super': 2,
|
||||||
|
curly: [2, 'multi-line'],
|
||||||
|
'dot-location': [2, 'property'],
|
||||||
|
'eol-last': 2,
|
||||||
|
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
||||||
|
'generator-star-spacing': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
before: true,
|
||||||
|
after: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'handle-callback-err': [2, '^(err|error)$'],
|
||||||
|
indent: [
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
SwitchCase: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'jsx-quotes': [2, 'prefer-single'],
|
||||||
|
'key-spacing': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
beforeColon: false,
|
||||||
|
afterColon: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'keyword-spacing': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
before: true,
|
||||||
|
after: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'new-cap': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
newIsCap: true,
|
||||||
|
capIsNew: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'new-parens': 2,
|
||||||
|
'no-array-constructor': 2,
|
||||||
|
'no-caller': 2,
|
||||||
|
'no-console': 'off',
|
||||||
|
'no-class-assign': 2,
|
||||||
|
'no-cond-assign': 2,
|
||||||
|
'no-const-assign': 2,
|
||||||
|
'no-control-regex': 0,
|
||||||
|
'no-delete-var': 2,
|
||||||
|
'no-dupe-args': 2,
|
||||||
|
'no-dupe-class-members': 2,
|
||||||
|
'no-dupe-keys': 2,
|
||||||
|
'no-duplicate-case': 2,
|
||||||
|
'no-empty-character-class': 2,
|
||||||
|
'no-empty-pattern': 2,
|
||||||
|
'no-eval': 2,
|
||||||
|
'no-ex-assign': 2,
|
||||||
|
'no-extend-native': 2,
|
||||||
|
'no-extra-bind': 2,
|
||||||
|
'no-extra-boolean-cast': 2,
|
||||||
|
'no-extra-parens': [2, 'functions'],
|
||||||
|
'no-fallthrough': 2,
|
||||||
|
'no-floating-decimal': 2,
|
||||||
|
'no-func-assign': 2,
|
||||||
|
'no-implied-eval': 2,
|
||||||
|
'no-inner-declarations': [2, 'functions'],
|
||||||
|
'no-invalid-regexp': 2,
|
||||||
|
'no-irregular-whitespace': 2,
|
||||||
|
'no-iterator': 2,
|
||||||
|
'no-label-var': 2,
|
||||||
|
'no-labels': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
allowLoop: false,
|
||||||
|
allowSwitch: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'no-lone-blocks': 2,
|
||||||
|
'no-mixed-spaces-and-tabs': 2,
|
||||||
|
'no-multi-spaces': 2,
|
||||||
|
'no-multi-str': 2,
|
||||||
|
'no-multiple-empty-lines': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
max: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'no-native-reassign': 2,
|
||||||
|
'no-negated-in-lhs': 2,
|
||||||
|
'no-new-object': 2,
|
||||||
|
'no-new-require': 2,
|
||||||
|
'no-new-symbol': 2,
|
||||||
|
'no-new-wrappers': 2,
|
||||||
|
'no-obj-calls': 2,
|
||||||
|
'no-octal': 2,
|
||||||
|
'no-octal-escape': 2,
|
||||||
|
'no-path-concat': 2,
|
||||||
|
'no-proto': 2,
|
||||||
|
'no-redeclare': 2,
|
||||||
|
'no-regex-spaces': 2,
|
||||||
|
'no-return-assign': [2, 'except-parens'],
|
||||||
|
'no-self-assign': 2,
|
||||||
|
'no-self-compare': 2,
|
||||||
|
'no-sequences': 2,
|
||||||
|
'no-shadow-restricted-names': 2,
|
||||||
|
'no-spaced-func': 2,
|
||||||
|
'no-sparse-arrays': 2,
|
||||||
|
'no-this-before-super': 2,
|
||||||
|
'no-throw-literal': 2,
|
||||||
|
'no-trailing-spaces': 2,
|
||||||
|
'no-undef': 2,
|
||||||
|
'no-undef-init': 2,
|
||||||
|
'no-unexpected-multiline': 2,
|
||||||
|
'no-unmodified-loop-condition': 2,
|
||||||
|
'no-unneeded-ternary': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
defaultAssignment: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'no-unreachable': 2,
|
||||||
|
'no-unsafe-finally': 2,
|
||||||
|
'no-unused-vars': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
vars: 'all',
|
||||||
|
args: 'none'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'no-useless-call': 2,
|
||||||
|
'no-useless-computed-key': 2,
|
||||||
|
'no-useless-constructor': 2,
|
||||||
|
'no-useless-escape': 0,
|
||||||
|
'no-whitespace-before-property': 2,
|
||||||
|
'no-with': 2,
|
||||||
|
'one-var': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
initialized: 'never'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'operator-linebreak': [
|
||||||
|
2,
|
||||||
|
'after',
|
||||||
|
{
|
||||||
|
overrides: {
|
||||||
|
'?': 'before',
|
||||||
|
':': 'before'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'padded-blocks': [2, 'never'],
|
||||||
|
quotes: [
|
||||||
|
2,
|
||||||
|
'single',
|
||||||
|
{
|
||||||
|
avoidEscape: true,
|
||||||
|
allowTemplateLiterals: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
semi: [2, 'never'],
|
||||||
|
'semi-spacing': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
before: false,
|
||||||
|
after: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'space-before-blocks': [2, 'always'],
|
||||||
|
// 'space-before-function-paren': [2, 'never'],
|
||||||
|
'space-in-parens': [2, 'never'],
|
||||||
|
'space-infix-ops': 2,
|
||||||
|
'space-unary-ops': [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
words: true,
|
||||||
|
nonwords: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'spaced-comment': [
|
||||||
|
2,
|
||||||
|
'always',
|
||||||
|
{
|
||||||
|
markers: ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'template-curly-spacing': [2, 'never'],
|
||||||
|
'use-isnan': 2,
|
||||||
|
'valid-typeof': 2,
|
||||||
|
'wrap-iife': [2, 'any'],
|
||||||
|
'yield-star-spacing': [2, 'both'],
|
||||||
|
yoda: [2, 'never'],
|
||||||
|
'prefer-const': 2,
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
||||||
|
'object-curly-spacing': [
|
||||||
|
2,
|
||||||
|
'always',
|
||||||
|
{
|
||||||
|
objectsInObjects: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'array-bracket-spacing': [2, 'never']
|
||||||
|
}
|
||||||
|
}
|
27
web/frps/.gitignore
vendored
27
web/frps/.gitignore
vendored
@ -1,6 +1,25 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules/
|
node_modules
|
||||||
dist/
|
/dist
|
||||||
npm-debug.log
|
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
.vscode/settings.json
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
package-lock.json
|
8
web/frps/.prettierrc
Normal file
8
web/frps/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"printWidth": 160,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"arrowParens": "avoid"
|
||||||
|
}
|
@ -1,7 +1,10 @@
|
|||||||
.PHONY: dist build
|
.PHONY: dist build
|
||||||
|
|
||||||
build:
|
build: install
|
||||||
@npm run build
|
@npm run build
|
||||||
|
|
||||||
dev: install
|
dev: install
|
||||||
@npm run dev
|
@npm run serve
|
||||||
|
|
||||||
|
install:
|
||||||
|
@npm install
|
||||||
|
25
web/frps/README.md
Normal file
25
web/frps/README.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# frps
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
3
web/frps/babel.config.js
Normal file
3
web/frps/babel.config.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: ['@vue/cli-plugin-babel/preset']
|
||||||
|
}
|
9334
web/frps/package-lock.json
generated
9334
web/frps/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,45 +4,43 @@
|
|||||||
"author": "fatedier",
|
"author": "fatedier",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "webpack-dev-server -d --inline --hot --env.dev",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "rimraf dist && webpack -p --progress --hide-modules"
|
"build": "vue-cli-service build",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^3.3.7",
|
"core-js": "^3.7.0",
|
||||||
"echarts": "^3.5.0",
|
"echarts": "^4.9.0",
|
||||||
"element-ui": "^2.3.8",
|
"element-ui": "^2.14.1",
|
||||||
"humanize-plus": "^1.8.2",
|
"humanize-plus": "^1.8.2",
|
||||||
"vue": "^2.5.16",
|
"vue": "^2.6.12",
|
||||||
"vue-resource": "^1.2.1",
|
"vue-router": "^3.4.9",
|
||||||
"vue-router": "^2.3.0",
|
"vuex": "^3.5.1",
|
||||||
"whatwg-fetch": "^2.0.3"
|
"whatwg-fetch": "^3.5.0"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^6.6.0",
|
"@vue/cli-plugin-babel": "~4.5.9",
|
||||||
"babel-core": "^6.21.0",
|
"@vue/cli-plugin-eslint": "~4.5.9",
|
||||||
"babel-eslint": "^7.1.1",
|
"@vue/cli-plugin-router": "~4.5.9",
|
||||||
"babel-loader": "^6.4.0",
|
"@vue/cli-plugin-vuex": "~4.5.9",
|
||||||
"babel-plugin-component": "^1.1.1",
|
"@vue/cli-service": "~4.5.9",
|
||||||
"babel-preset-es2015": "^6.13.2",
|
"@vue/eslint-config-standard": "^5.1.2",
|
||||||
"css-loader": "^0.27.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"eslint": "^3.12.2",
|
"eslint": "^7.14.0",
|
||||||
"eslint-config-enough": "^0.2.2",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-loader": "^1.6.3",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"file-loader": "^0.10.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"html-loader": "^0.4.5",
|
"eslint-plugin-standard": "^4.1.0",
|
||||||
"html-webpack-plugin": "^2.24.1",
|
"eslint-plugin-vue": "^7.1.0",
|
||||||
"less": "^3.0.4",
|
"less": "^3.12.2",
|
||||||
"less-loader": "^4.1.0",
|
"less-loader": "^7.1.0",
|
||||||
"postcss-loader": "^1.3.3",
|
"node-sass": "^5.0.0",
|
||||||
"rimraf": "^2.5.4",
|
"sass-loader": "^10.1.0",
|
||||||
"style-loader": "^0.13.2",
|
"vue-template-compiler": "^2.6.12"
|
||||||
"url-loader": "^1.0.1",
|
},
|
||||||
"vue-loader": "^15.0.10",
|
"browserslist": [
|
||||||
"vue-template-compiler": "^2.1.8",
|
"> 1%",
|
||||||
"webpack": "^2.2.0-rc.4",
|
"last 2 versions",
|
||||||
"webpack-dev-server": "^3.1.4"
|
"not dead"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: [
|
|
||||||
require('autoprefixer')()
|
|
||||||
]
|
|
||||||
}
|
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
17
web/frps/public/index.html
Normal file
17
web/frps/public/index.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -6,9 +6,9 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</header>
|
</header>
|
||||||
<section>
|
<section>
|
||||||
<el-row :gutter="20">
|
<el-row>
|
||||||
<el-col id="side-nav" :xs="24" :md="4">
|
<el-col id="side-nav" :xs="24" :md="4">
|
||||||
<el-menu default-active="1" mode="vertical" theme="light" router="false" @select="handleSelect">
|
<el-menu default-active="1" mode="vertical" theme="light" router @select="handleSelect">
|
||||||
<el-menu-item index="/">Overview</el-menu-item>
|
<el-menu-item index="/">Overview</el-menu-item>
|
||||||
<el-submenu index="/proxies">
|
<el-submenu index="/proxies">
|
||||||
<template slot="title">Proxies</template>
|
<template slot="title">Proxies</template>
|
||||||
@ -24,21 +24,28 @@
|
|||||||
|
|
||||||
<el-col :xs="24" :md="20">
|
<el-col :xs="24" :md="20">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<router-view></router-view>
|
<router-view v-if="serverInfo" />
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</section>
|
</section>
|
||||||
<footer></footer>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
computed: {
|
||||||
|
serverInfo() {
|
||||||
|
return this.$store.state.serverInfo
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
this.$store.dispatch('fetchServerInfo')
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleSelect(key, path) {
|
handleSelect(key, path) {
|
||||||
if (key == '') {
|
if (key === '') {
|
||||||
window.open("https://github.com/fatedier/frp")
|
window.open('https://github.com/fatedier/frp')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,7 +65,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header-color {
|
.header-color {
|
||||||
background: #58B7FF;
|
background: #58b7ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#content {
|
||||||
|
@ -44,8 +44,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :md="12">
|
<el-col :md="12">
|
||||||
<div id="traffic" style="width: 400px;height:250px;margin-bottom: 30px;"></div>
|
<div id="traffic" style="width: 400px; height: 250px; margin-bottom: 30px" />
|
||||||
<div id="proxies" style="width: 400px;height:250px;"></div>
|
<div id="proxies" style="width: 400px; height: 250px" />
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
@ -70,71 +70,65 @@
|
|||||||
proxy_counts: ''
|
proxy_counts: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
computed: {
|
||||||
this.fetchData()
|
serverInfo() {
|
||||||
|
return this.$store.state.serverInfo
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
mounted() {
|
||||||
'$route': 'fetchData'
|
this.initData()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchData() {
|
initData() {
|
||||||
fetch('/api/serverinfo', {credentials: 'include'})
|
console.log(!!this.serverInfo, this.serverInfo)
|
||||||
.then(res => {
|
if (!this.serverInfo) return
|
||||||
return res.json()
|
|
||||||
}).then(json => {
|
this.version = this.serverInfo.version
|
||||||
this.version = json.version
|
this.bind_port = this.serverInfo.bind_port
|
||||||
this.bind_port = json.bind_port
|
this.bind_udp_port = this.serverInfo.bind_udp_port
|
||||||
this.bind_udp_port = json.bind_udp_port
|
if (this.bind_udp_port === 0) {
|
||||||
if (this.bind_udp_port == 0) {
|
this.bind_udp_port = 'disable'
|
||||||
this.bind_udp_port = "disable"
|
|
||||||
}
|
}
|
||||||
this.vhost_http_port = json.vhost_http_port
|
this.vhost_http_port = this.serverInfo.vhost_http_port
|
||||||
if (this.vhost_http_port == 0) {
|
if (this.vhost_http_port === 0) {
|
||||||
this.vhost_http_port = "disable"
|
this.vhost_http_port = 'disable'
|
||||||
}
|
}
|
||||||
this.vhost_https_port = json.vhost_https_port
|
this.vhost_https_port = this.serverInfo.vhost_https_port
|
||||||
if (this.vhost_https_port == 0) {
|
if (this.vhost_https_port === 0) {
|
||||||
this.vhost_https_port = "disable"
|
this.vhost_https_port = 'disable'
|
||||||
}
|
}
|
||||||
this.subdomain_host = json.subdomain_host
|
this.subdomain_host = this.serverInfo.subdomain_host
|
||||||
this.max_pool_count = json.max_pool_count
|
this.max_pool_count = this.serverInfo.max_pool_count
|
||||||
this.max_ports_per_client = json.max_ports_per_client
|
this.max_ports_per_client = this.serverInfo.max_ports_per_client
|
||||||
if (this.max_ports_per_client == 0) {
|
if (this.max_ports_per_client === 0) {
|
||||||
this.max_ports_per_client = "no limit"
|
this.max_ports_per_client = 'no limit'
|
||||||
}
|
}
|
||||||
this.heart_beat_timeout = json.heart_beat_timeout
|
this.heart_beat_timeout = this.serverInfo.heart_beat_timeout
|
||||||
this.client_counts = json.client_counts
|
this.client_counts = this.serverInfo.client_counts
|
||||||
this.cur_conns = json.cur_conns
|
this.cur_conns = this.serverInfo.cur_conns
|
||||||
this.proxy_counts = 0
|
this.proxy_counts = 0
|
||||||
if (json.proxy_type_count != null) {
|
if (this.serverInfo.proxy_type_count != null) {
|
||||||
if (json.proxy_type_count.tcp != null) {
|
if (this.serverInfo.proxy_type_count.tcp != null) {
|
||||||
this.proxy_counts += json.proxy_type_count.tcp
|
this.proxy_counts += this.serverInfo.proxy_type_count.tcp
|
||||||
}
|
}
|
||||||
if (json.proxy_type_count.udp != null) {
|
if (this.serverInfo.proxy_type_count.udp != null) {
|
||||||
this.proxy_counts += json.proxy_type_count.udp
|
this.proxy_counts += this.serverInfo.proxy_type_count.udp
|
||||||
}
|
}
|
||||||
if (json.proxy_type_count.http != null) {
|
if (this.serverInfo.proxy_type_count.http != null) {
|
||||||
this.proxy_counts += json.proxy_type_count.http
|
this.proxy_counts += this.serverInfo.proxy_type_count.http
|
||||||
}
|
}
|
||||||
if (json.proxy_type_count.https != null) {
|
if (this.serverInfo.proxy_type_count.https != null) {
|
||||||
this.proxy_counts += json.proxy_type_count.https
|
this.proxy_counts += this.serverInfo.proxy_type_count.https
|
||||||
}
|
}
|
||||||
if (json.proxy_type_count.stcp != null) {
|
if (this.serverInfo.proxy_type_count.stcp != null) {
|
||||||
this.proxy_counts += json.proxy_type_count.stcp
|
this.proxy_counts += this.serverInfo.proxy_type_count.stcp
|
||||||
}
|
}
|
||||||
if (json.proxy_type_count.xtcp != null) {
|
if (this.serverInfo.proxy_type_count.xtcp != null) {
|
||||||
this.proxy_counts += json.proxy_type_count.xtcp
|
this.proxy_counts += this.serverInfo.proxy_type_count.xtcp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DrawTrafficChart('traffic', json.total_traffic_in, json.total_traffic_out)
|
DrawTrafficChart('traffic', this.serverInfo.total_traffic_in, this.serverInfo.total_traffic_out)
|
||||||
DrawProxyChart('proxies', json)
|
DrawProxyChart('proxies', this.serverInfo)
|
||||||
}).catch( err => {
|
|
||||||
this.$message({
|
|
||||||
showClose: true,
|
|
||||||
message: 'Get server info from frps failed!',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,7 +138,7 @@
|
|||||||
.source {
|
.source {
|
||||||
border: 1px solid #eaeefb;
|
border: 1px solid #eaeefb;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: .2s;
|
transition: 0.2s;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,13 +3,8 @@
|
|||||||
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
||||||
<el-table-column type="expand">
|
<el-table-column type="expand">
|
||||||
<template slot-scope="props">
|
<template slot-scope="props">
|
||||||
<el-popover
|
<el-popover ref="popover4" placement="right" width="600" style="margin-left: 0px" trigger="click">
|
||||||
ref="popover4"
|
<my-traffic-chart :proxy-name="props.row.name" />
|
||||||
placement="right"
|
|
||||||
width="600"
|
|
||||||
style="margin-left:0px"
|
|
||||||
trigger="click">
|
|
||||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
|
|
||||||
</el-popover>
|
</el-popover>
|
||||||
|
|
||||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom: 10px">Traffic Statistics</el-button>
|
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom: 10px">Traffic Statistics</el-button>
|
||||||
@ -48,40 +43,15 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column label="Name" prop="name" sortable />
|
||||||
label="Name"
|
<el-table-column label="Port" prop="port" sortable />
|
||||||
prop="name"
|
<el-table-column label="Connections" prop="conns" sortable />
|
||||||
sortable>
|
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
|
||||||
</el-table-column>
|
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
|
||||||
<el-table-column
|
<el-table-column label="status" prop="status" sortable>
|
||||||
label="Port"
|
|
||||||
prop="port"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Connections"
|
|
||||||
prop="conns"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Traffic In"
|
|
||||||
prop="traffic_in"
|
|
||||||
:formatter="formatTrafficIn"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Traffic Out"
|
|
||||||
prop="traffic_out"
|
|
||||||
:formatter="formatTrafficOut"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="status"
|
|
||||||
prop="status"
|
|
||||||
sortable>
|
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
|
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
|
||||||
<el-tag type="danger" v-else>{{ 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>
|
</el-table>
|
||||||
@ -89,24 +59,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Humanize from 'humanize-plus';
|
import Humanize from 'humanize-plus'
|
||||||
import Traffic from './Traffic.vue'
|
import Traffic from './Traffic.vue'
|
||||||
import {
|
import { HttpProxy } from '../utils/proxy.js'
|
||||||
HttpProxy
|
|
||||||
} from '../utils/proxy.js'
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
'my-traffic-chart': Traffic
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
proxies: null,
|
proxies: [],
|
||||||
vhost_http_port: "",
|
vhost_http_port: '',
|
||||||
subdomain_host: ""
|
subdomain_host: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
computed: {
|
||||||
this.fetchData()
|
serverInfo() {
|
||||||
|
return this.$store.state.serverInfo
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
mounted() {
|
||||||
'$route': 'fetchData'
|
this.initData()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatTrafficIn(row, column) {
|
formatTrafficIn(row, column) {
|
||||||
@ -115,34 +88,20 @@
|
|||||||
formatTrafficOut(row, column) {
|
formatTrafficOut(row, column) {
|
||||||
return Humanize.fileSize(row.traffic_out)
|
return Humanize.fileSize(row.traffic_out)
|
||||||
},
|
},
|
||||||
fetchData() {
|
async initData() {
|
||||||
fetch('/api/serverinfo', {credentials: 'include'})
|
if (!this.serverInfo) return
|
||||||
.then(res => {
|
this.vhost_http_port = this.serverInfo.vhost_http_port
|
||||||
return res.json()
|
this.subdomain_host = this.serverInfo.subdomain_host
|
||||||
}).then(json => {
|
if (this.vhost_http_port == null || this.vhost_http_port === 0) return
|
||||||
this.vhost_http_port = json.vhost_http_port
|
|
||||||
this.subdomain_host = json.subdomain_host
|
const json = await this.$fetch('proxy/http')
|
||||||
if (this.vhost_http_port == null || this.vhost_http_port == 0) {
|
if (!json) return
|
||||||
return
|
|
||||||
} else {
|
this.proxies = []
|
||||||
fetch('/api/proxy/http', {credentials: 'include'})
|
for (const proxyStats of json.proxies) {
|
||||||
.then(res => {
|
|
||||||
return res.json()
|
|
||||||
}).then(json => {
|
|
||||||
this.proxies = new Array()
|
|
||||||
for (let proxyStats of json.proxies) {
|
|
||||||
this.proxies.push(new HttpProxy(proxyStats, this.vhost_http_port, this.subdomain_host))
|
this.proxies.push(new HttpProxy(proxyStats, this.vhost_http_port, this.subdomain_host))
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
'my-traffic-chart': Traffic
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
|
@ -3,13 +3,8 @@
|
|||||||
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
||||||
<el-table-column type="expand">
|
<el-table-column type="expand">
|
||||||
<template slot-scope="props">
|
<template slot-scope="props">
|
||||||
<el-popover
|
<el-popover ref="popover4" placement="right" width="600" style="margin-left: 0px" trigger="click">
|
||||||
ref="popover4"
|
<my-traffic-chart :proxy-name="props.row.name" />
|
||||||
placement="right"
|
|
||||||
width="600"
|
|
||||||
style="margin-left:0px"
|
|
||||||
trigger="click">
|
|
||||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
|
|
||||||
</el-popover>
|
</el-popover>
|
||||||
|
|
||||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom: 10px">Traffic Statistics</el-button>
|
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom: 10px">Traffic Statistics</el-button>
|
||||||
@ -42,66 +37,43 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column label="Name" prop="name" sortable />
|
||||||
label="Name"
|
<el-table-column label="Port" prop="port" sortable />
|
||||||
prop="name"
|
<el-table-column label="Connections" prop="conns" sortable />
|
||||||
sortable>
|
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
|
||||||
</el-table-column>
|
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
|
||||||
<el-table-column
|
<el-table-column label="status" prop="status" sortable>
|
||||||
label="Port"
|
|
||||||
prop="port"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Connections"
|
|
||||||
prop="conns"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Traffic In"
|
|
||||||
prop="traffic_in"
|
|
||||||
:formatter="formatTrafficIn"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Traffic Out"
|
|
||||||
prop="traffic_out"
|
|
||||||
:formatter="formatTrafficOut"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="status"
|
|
||||||
prop="status"
|
|
||||||
sortable>
|
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
|
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
|
||||||
<el-tag type="danger" v-else>{{ 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>
|
</el-table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Humanize from 'humanize-plus';
|
import Humanize from 'humanize-plus'
|
||||||
import Traffic from './Traffic.vue'
|
import Traffic from './Traffic.vue'
|
||||||
import {
|
import { HttpsProxy } from '../utils/proxy.js'
|
||||||
HttpsProxy
|
|
||||||
} from '../utils/proxy.js'
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
'my-traffic-chart': Traffic
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
proxies: null,
|
proxies: [],
|
||||||
vhost_https_port: '',
|
vhost_https_port: '',
|
||||||
subdomain_host: ''
|
subdomain_host: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
computed: {
|
||||||
this.fetchData()
|
serverInfo() {
|
||||||
|
return this.$store.state.serverInfo
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
mounted() {
|
||||||
'$route': 'fetchData'
|
this.initData()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatTrafficIn(row, column) {
|
formatTrafficIn(row, column) {
|
||||||
@ -110,34 +82,21 @@
|
|||||||
formatTrafficOut(row, column) {
|
formatTrafficOut(row, column) {
|
||||||
return Humanize.fileSize(row.traffic_out)
|
return Humanize.fileSize(row.traffic_out)
|
||||||
},
|
},
|
||||||
fetchData() {
|
async initData() {
|
||||||
fetch('/api/serverinfo', {credentials: 'include'})
|
if (!this.serverInfo) return
|
||||||
.then(res => {
|
|
||||||
return res.json()
|
this.vhost_https_port = this.serverInfo.vhost_https_port
|
||||||
}).then(json => {
|
this.subdomain_host = this.serverInfo.subdomain_host
|
||||||
this.vhost_https_port = json.vhost_https_port
|
if (this.vhost_https_port == null || this.vhost_https_port === 0) return
|
||||||
this.subdomain_host = json.subdomain_host
|
|
||||||
if (this.vhost_https_port == null || this.vhost_https_port == 0) {
|
const json = await this.$fetch('proxy/https')
|
||||||
return
|
if (!json) return
|
||||||
} else {
|
|
||||||
fetch('/api/proxy/https', {credentials: 'include'})
|
this.proxies = []
|
||||||
.then(res => {
|
for (const proxyStats of json.proxies) {
|
||||||
return res.json()
|
|
||||||
}).then(json => {
|
|
||||||
this.proxies = new Array()
|
|
||||||
for (let proxyStats of json.proxies) {
|
|
||||||
this.proxies.push(new HttpsProxy(proxyStats, this.vhost_https_port, this.subdomain_host))
|
this.proxies.push(new HttpsProxy(proxyStats, this.vhost_https_port, this.subdomain_host))
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
'my-traffic-chart': Traffic
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
|
@ -3,16 +3,13 @@
|
|||||||
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
||||||
<el-table-column type="expand">
|
<el-table-column type="expand">
|
||||||
<template slot-scope="props">
|
<template slot-scope="props">
|
||||||
<el-popover
|
<el-popover ref="popover4" placement="right" width="600" style="margin-left: 0px" trigger="click">
|
||||||
ref="popover4"
|
<my-traffic-chart :proxy-name="props.row.name" />
|
||||||
placement="right"
|
|
||||||
width="600"
|
|
||||||
style="margin-left:0px"
|
|
||||||
trigger="click">
|
|
||||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
|
|
||||||
</el-popover>
|
</el-popover>
|
||||||
|
|
||||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" :name="props.row.name" style="margin-bottom:10px" @click="fetchData2">Traffic Statistics</el-button>
|
<el-button v-popover:popover4 type="primary" size="small" icon="view" :name="props.row.name" style="margin-bottom: 10px" @click="fetchData2">
|
||||||
|
Traffic Statistics
|
||||||
|
</el-button>
|
||||||
|
|
||||||
<el-form label-position="left" inline class="demo-table-expand">
|
<el-form label-position="left" inline class="demo-table-expand">
|
||||||
<el-form-item label="Name">
|
<el-form-item label="Name">
|
||||||
@ -36,35 +33,14 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column label="Name" prop="name" sortable />
|
||||||
label="Name"
|
<el-table-column label="Connections" prop="conns" sortable />
|
||||||
prop="name"
|
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
|
||||||
sortable>
|
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
|
||||||
</el-table-column>
|
<el-table-column label="status" prop="status" sortable>
|
||||||
<el-table-column
|
|
||||||
label="Connections"
|
|
||||||
prop="conns"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Traffic In"
|
|
||||||
prop="traffic_in"
|
|
||||||
:formatter="formatTrafficIn"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Traffic Out"
|
|
||||||
prop="traffic_out"
|
|
||||||
:formatter="formatTrafficOut"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="status"
|
|
||||||
prop="status"
|
|
||||||
sortable>
|
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
|
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
|
||||||
<el-tag type="danger" v-else>{{ 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>
|
</el-table>
|
||||||
@ -76,16 +52,16 @@
|
|||||||
import Traffic from './Traffic.vue'
|
import Traffic from './Traffic.vue'
|
||||||
import { StcpProxy } from '../utils/proxy.js'
|
import { StcpProxy } from '../utils/proxy.js'
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
'my-traffic-chart': Traffic
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
proxies: null
|
proxies: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
mounted() {
|
||||||
this.fetchData()
|
this.initData()
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'$route': 'fetchData'
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatTrafficIn(row, column) {
|
formatTrafficIn(row, column) {
|
||||||
@ -94,20 +70,15 @@
|
|||||||
formatTrafficOut(row, column) {
|
formatTrafficOut(row, column) {
|
||||||
return Humanize.fileSize(row.traffic_out)
|
return Humanize.fileSize(row.traffic_out)
|
||||||
},
|
},
|
||||||
fetchData() {
|
async initData() {
|
||||||
fetch('/api/proxy/stcp', {credentials: 'include'})
|
const json = await this.$fetch('proxy/stcp')
|
||||||
.then(res => {
|
if (!json) return
|
||||||
return res.json()
|
|
||||||
}).then(json => {
|
this.proxies = []
|
||||||
this.proxies = new Array()
|
for (const proxyStats of json.proxies) {
|
||||||
for (let proxyStats of json.proxies) {
|
|
||||||
this.proxies.push(new StcpProxy(proxyStats))
|
this.proxies.push(new StcpProxy(proxyStats))
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
components: {
|
|
||||||
'my-traffic-chart': Traffic
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,16 +3,13 @@
|
|||||||
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
||||||
<el-table-column type="expand">
|
<el-table-column type="expand">
|
||||||
<template slot-scope="props">
|
<template slot-scope="props">
|
||||||
<el-popover
|
<el-popover placement="right" width="600" style="margin-left: 0px" trigger="click">
|
||||||
ref="popover4"
|
<my-traffic-chart :proxy-name="props.row.name" />
|
||||||
placement="right"
|
|
||||||
width="600"
|
|
||||||
style="margin-left:0px"
|
|
||||||
trigger="click">
|
|
||||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
|
|
||||||
</el-popover>
|
|
||||||
|
|
||||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" :name="props.row.name" style="margin-bottom:10px" @click="fetchData2">Traffic Statistics</el-button>
|
<el-button slot="reference" type="primary" size="small" icon="view" :name="props.row.name" style="margin-bottom: 10px">
|
||||||
|
Traffic Statistics
|
||||||
|
</el-button>
|
||||||
|
</el-popover>
|
||||||
|
|
||||||
<el-form label-position="left" inline class="demo-table-expand">
|
<el-form label-position="left" inline class="demo-table-expand">
|
||||||
<el-form-item label="Name">
|
<el-form-item label="Name">
|
||||||
@ -39,40 +36,15 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column label="Name" prop="name" sortable />
|
||||||
label="Name"
|
<el-table-column label="Port" prop="port" sortable />
|
||||||
prop="name"
|
<el-table-column label="Connections" prop="conns" sortable />
|
||||||
sortable>
|
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
|
||||||
</el-table-column>
|
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
|
||||||
<el-table-column
|
<el-table-column label="status" prop="status" sortable>
|
||||||
label="Port"
|
|
||||||
prop="port"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Connections"
|
|
||||||
prop="conns"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Traffic In"
|
|
||||||
prop="traffic_in"
|
|
||||||
:formatter="formatTrafficIn"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Traffic Out"
|
|
||||||
prop="traffic_out"
|
|
||||||
:formatter="formatTrafficOut"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="status"
|
|
||||||
prop="status"
|
|
||||||
sortable>
|
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
|
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
|
||||||
<el-tag type="danger" v-else>{{ 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>
|
</el-table>
|
||||||
@ -84,16 +56,16 @@
|
|||||||
import Traffic from './Traffic.vue'
|
import Traffic from './Traffic.vue'
|
||||||
import { TcpProxy } from '../utils/proxy.js'
|
import { TcpProxy } from '../utils/proxy.js'
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
'my-traffic-chart': Traffic
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
proxies: null
|
proxies: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
mounted() {
|
||||||
this.fetchData()
|
this.initData()
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'$route': 'fetchData'
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatTrafficIn(row, column) {
|
formatTrafficIn(row, column) {
|
||||||
@ -102,20 +74,15 @@
|
|||||||
formatTrafficOut(row, column) {
|
formatTrafficOut(row, column) {
|
||||||
return Humanize.fileSize(row.traffic_out)
|
return Humanize.fileSize(row.traffic_out)
|
||||||
},
|
},
|
||||||
fetchData() {
|
async initData() {
|
||||||
fetch('/api/proxy/tcp', {credentials: 'include'})
|
const json = await this.$fetch('proxy/tcp')
|
||||||
.then(res => {
|
if (!json) return
|
||||||
return res.json()
|
|
||||||
}).then(json => {
|
this.proxies = []
|
||||||
this.proxies = new Array()
|
for (const proxyStats of json.proxies) {
|
||||||
for (let proxyStats of json.proxies) {
|
|
||||||
this.proxies.push(new TcpProxy(proxyStats))
|
this.proxies.push(new TcpProxy(proxyStats))
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
components: {
|
|
||||||
'my-traffic-chart': Traffic
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,13 +3,8 @@
|
|||||||
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
<el-table :data="proxies" :default-sort="{ prop: 'name', order: 'ascending' }" style="width: 100%">
|
||||||
<el-table-column type="expand">
|
<el-table-column type="expand">
|
||||||
<template slot-scope="props">
|
<template slot-scope="props">
|
||||||
<el-popover
|
<el-popover ref="popover4" placement="right" width="600" style="margin-left: 0px" trigger="click">
|
||||||
ref="popover4"
|
<my-traffic-chart :proxy-name="props.row.name" />
|
||||||
placement="right"
|
|
||||||
width="600"
|
|
||||||
style="margin-left:0px"
|
|
||||||
trigger="click">
|
|
||||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
|
|
||||||
</el-popover>
|
</el-popover>
|
||||||
|
|
||||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom: 10px">Traffic Statistics</el-button>
|
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom: 10px">Traffic Statistics</el-button>
|
||||||
@ -39,40 +34,15 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column label="Name" prop="name" sortable />
|
||||||
label="Name"
|
<el-table-column label="Port" prop="port" sortable />
|
||||||
prop="name"
|
<el-table-column label="Connections" prop="conns" sortable />
|
||||||
sortable>
|
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
|
||||||
</el-table-column>
|
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
|
||||||
<el-table-column
|
<el-table-column label="status" prop="status" sortable>
|
||||||
label="Port"
|
|
||||||
prop="port"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Connections"
|
|
||||||
prop="conns"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Traffic In"
|
|
||||||
prop="traffic_in"
|
|
||||||
:formatter="formatTrafficIn"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="Traffic Out"
|
|
||||||
prop="traffic_out"
|
|
||||||
:formatter="formatTrafficOut"
|
|
||||||
sortable>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="status"
|
|
||||||
prop="status"
|
|
||||||
sortable>
|
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
|
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
|
||||||
<el-tag type="danger" v-else>{{ 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>
|
</el-table>
|
||||||
@ -80,22 +50,20 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Humanize from 'humanize-plus';
|
import Humanize from 'humanize-plus'
|
||||||
import Traffic from './Traffic.vue'
|
import Traffic from './Traffic.vue'
|
||||||
import {
|
import { UdpProxy } from '../utils/proxy.js'
|
||||||
UdpProxy
|
|
||||||
} from '../utils/proxy.js'
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
'my-traffic-chart': Traffic
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
proxies: null
|
proxies: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
mounted() {
|
||||||
this.fetchData()
|
this.initData()
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'$route': 'fetchData'
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatTrafficIn(row, column) {
|
formatTrafficIn(row, column) {
|
||||||
@ -104,23 +72,16 @@
|
|||||||
formatTrafficOut(row, column) {
|
formatTrafficOut(row, column) {
|
||||||
return Humanize.fileSize(row.traffic_out)
|
return Humanize.fileSize(row.traffic_out)
|
||||||
},
|
},
|
||||||
fetchData() {
|
async initData() {
|
||||||
fetch('/api/proxy/udp', {credentials: 'include'})
|
const json = await this.$fetch('proxy/udp')
|
||||||
.then(res => {
|
if (!json) return
|
||||||
return res.json()
|
|
||||||
}).then(json => {
|
this.proxies = []
|
||||||
this.proxies = new Array()
|
for (const proxyStats of json.proxies) {
|
||||||
for (let proxyStats of json.proxies) {
|
|
||||||
this.proxies.push(new UdpProxy(proxyStats))
|
this.proxies.push(new UdpProxy(proxyStats))
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
components: {
|
|
||||||
'my-traffic-chart': Traffic
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
|
@ -1,36 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :id="proxy_name" style="width: 600px;height:400px;"></div>
|
<div :id="proxyName" style="width: 600px; height: 400px" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { DrawProxyTrafficChart } from '../utils/chart.js'
|
import { DrawProxyTrafficChart } from '../utils/chart.js'
|
||||||
export default {
|
export default {
|
||||||
props: ['proxy_name'],
|
props: {
|
||||||
created() {
|
proxyName: {
|
||||||
this.fetchData()
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initData()
|
||||||
},
|
},
|
||||||
//watch: {
|
|
||||||
//'$route': 'fetchData'
|
|
||||||
//},
|
|
||||||
methods: {
|
methods: {
|
||||||
fetchData() {
|
async initData() {
|
||||||
let url = '/api/traffic/' + this.proxy_name
|
const json = await this.$fetch(`traffic/${this.proxyName}`)
|
||||||
fetch(url, {credentials: 'include'})
|
if (!json) return
|
||||||
.then(res => {
|
|
||||||
return res.json()
|
DrawProxyTrafficChart(this.proxyName, json.traffic_in, json.traffic_out)
|
||||||
}).then(json => {
|
|
||||||
DrawProxyTrafficChart(this.proxy_name, json.traffic_in, json.traffic_out)
|
|
||||||
}).catch( err => {
|
|
||||||
this.$message({
|
|
||||||
showClose: true,
|
|
||||||
message: 'Get server info from frps failed!' + err,
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
</style>
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>frps dashboard</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<!--<script src="https://code.jquery.com/jquery-3.2.0.min.js"></script>-->
|
|
||||||
<!--<script src="//cdn.bootcss.com/echarts/3.4.0/echarts.min.js"></script>-->
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,19 +1,6 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
// import ElementUI from 'element-ui'
|
// import ElementUI from 'element-ui'
|
||||||
import {
|
import { Button, Form, FormItem, Row, Col, Table, TableColumn, Popover, Menu, Submenu, MenuItem, Tag, Message } from 'element-ui'
|
||||||
Button,
|
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
Row,
|
|
||||||
Col,
|
|
||||||
Table,
|
|
||||||
TableColumn,
|
|
||||||
Popover,
|
|
||||||
Menu,
|
|
||||||
Submenu,
|
|
||||||
MenuItem,
|
|
||||||
Tag
|
|
||||||
} from 'element-ui'
|
|
||||||
import lang from 'element-ui/lib/locale/lang/en'
|
import lang from 'element-ui/lib/locale/lang/en'
|
||||||
import locale from 'element-ui/lib/locale'
|
import locale from 'element-ui/lib/locale'
|
||||||
import 'element-ui/lib/theme-chalk/index.css'
|
import 'element-ui/lib/theme-chalk/index.css'
|
||||||
@ -21,6 +8,7 @@ import './utils/less/custom.less'
|
|||||||
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
import store from '@/store'
|
||||||
import 'whatwg-fetch'
|
import 'whatwg-fetch'
|
||||||
|
|
||||||
locale.use(lang)
|
locale.use(lang)
|
||||||
@ -37,12 +25,15 @@ Vue.use(Menu)
|
|||||||
Vue.use(Submenu)
|
Vue.use(Submenu)
|
||||||
Vue.use(MenuItem)
|
Vue.use(MenuItem)
|
||||||
Vue.use(Tag)
|
Vue.use(Tag)
|
||||||
|
Vue.prototype.$message = Message
|
||||||
|
|
||||||
|
import fetch from '@/utils/fetch'
|
||||||
|
Vue.prototype.$fetch = fetch
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
|
||||||
router,
|
router,
|
||||||
template: '<App/>',
|
store,
|
||||||
components: { App }
|
render: h => h(App)
|
||||||
})
|
}).$mount('#app')
|
||||||
|
@ -10,29 +10,36 @@ import ProxiesStcp from '../components/ProxiesStcp.vue'
|
|||||||
Vue.use(Router)
|
Vue.use(Router)
|
||||||
|
|
||||||
export default new Router({
|
export default new Router({
|
||||||
routes: [{
|
routes: [
|
||||||
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'Overview',
|
name: 'Overview',
|
||||||
component: Overview
|
component: Overview
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
path: '/proxies/tcp',
|
path: '/proxies/tcp',
|
||||||
name: 'ProxiesTcp',
|
name: 'ProxiesTcp',
|
||||||
component: ProxiesTcp
|
component: ProxiesTcp
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
path: '/proxies/udp',
|
path: '/proxies/udp',
|
||||||
name: 'ProxiesUdp',
|
name: 'ProxiesUdp',
|
||||||
component: ProxiesUdp
|
component: ProxiesUdp
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
path: '/proxies/http',
|
path: '/proxies/http',
|
||||||
name: 'ProxiesHttp',
|
name: 'ProxiesHttp',
|
||||||
component: ProxiesHttp
|
component: ProxiesHttp
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
path: '/proxies/https',
|
path: '/proxies/https',
|
||||||
name: 'ProxiesHttps',
|
name: 'ProxiesHttps',
|
||||||
component: ProxiesHttps
|
component: ProxiesHttps
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
path: '/proxies/stcp',
|
path: '/proxies/stcp',
|
||||||
name: 'ProxiesStcp',
|
name: 'ProxiesStcp',
|
||||||
component: ProxiesStcp
|
component: ProxiesStcp
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
24
web/frps/src/store/index.js
Normal file
24
web/frps/src/store/index.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
import fetch from '@/utils/fetch'
|
||||||
|
Vue.use(Vuex)
|
||||||
|
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
state: {
|
||||||
|
serverInfo: null
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
SET_SERVER_INFO(state, serverInfo) {
|
||||||
|
state.serverInfo = serverInfo
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async fetchServerInfo({ commit }) {
|
||||||
|
const json = await fetch('serverinfo')
|
||||||
|
commit('SET_SERVER_INFO', json || null)
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default store
|
@ -1,17 +1,17 @@
|
|||||||
import Humanize from "humanize-plus"
|
import Humanize from 'humanize-plus'
|
||||||
import echarts from "echarts/lib/echarts"
|
import echarts from 'echarts/lib/echarts'
|
||||||
|
|
||||||
import "echarts/theme/macarons"
|
import 'echarts/theme/macarons'
|
||||||
import "echarts/lib/chart/bar"
|
import 'echarts/lib/chart/bar'
|
||||||
import "echarts/lib/chart/pie"
|
import 'echarts/lib/chart/pie'
|
||||||
import "echarts/lib/component/tooltip"
|
import 'echarts/lib/component/tooltip'
|
||||||
import "echarts/lib/component/title"
|
import 'echarts/lib/component/title'
|
||||||
|
|
||||||
function DrawTrafficChart(elementId, trafficIn, trafficOut) {
|
function DrawTrafficChart(elementId, trafficIn, trafficOut) {
|
||||||
let myChart = echarts.init(document.getElementById(elementId), 'macarons');
|
const myChart = echarts.init(document.getElementById(elementId), 'macarons')
|
||||||
myChart.showLoading()
|
myChart.showLoading()
|
||||||
|
|
||||||
let option = {
|
const option = {
|
||||||
title: {
|
title: {
|
||||||
text: 'Network Traffic',
|
text: 'Network Traffic',
|
||||||
subtext: 'today',
|
subtext: 'today',
|
||||||
@ -20,20 +20,24 @@ function DrawTrafficChart(elementId, trafficIn, trafficOut) {
|
|||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
formatter: function(v) {
|
formatter: function(v) {
|
||||||
return Humanize.fileSize(v.data.value) + " (" + v.percent + "%)"
|
return Humanize.fileSize(v.data.value) + ' (' + v.percent + '%)'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
series: [{
|
series: [
|
||||||
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
radius: '55%',
|
radius: '55%',
|
||||||
center: ['50%', '60%'],
|
center: ['50%', '60%'],
|
||||||
data: [{
|
data: [
|
||||||
|
{
|
||||||
value: trafficIn,
|
value: trafficIn,
|
||||||
name: 'Traffic In'
|
name: 'Traffic In'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
value: trafficOut,
|
value: trafficOut,
|
||||||
name: 'Traffic Out'
|
name: 'Traffic Out'
|
||||||
}, ],
|
}
|
||||||
|
],
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
emphasis: {
|
emphasis: {
|
||||||
shadowBlur: 10,
|
shadowBlur: 10,
|
||||||
@ -41,9 +45,10 @@ function DrawTrafficChart(elementId, trafficIn, trafficOut) {
|
|||||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
};
|
]
|
||||||
myChart.setOption(option);
|
}
|
||||||
|
myChart.setOption(option)
|
||||||
myChart.hideLoading()
|
myChart.hideLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,10 +71,10 @@ function DrawProxyChart(elementId, serverInfo) {
|
|||||||
if (serverInfo.proxy_type_count.xtcp == null) {
|
if (serverInfo.proxy_type_count.xtcp == null) {
|
||||||
serverInfo.proxy_type_count.xtcp = 0
|
serverInfo.proxy_type_count.xtcp = 0
|
||||||
}
|
}
|
||||||
let myChart = echarts.init(document.getElementById(elementId), 'macarons')
|
const myChart = echarts.init(document.getElementById(elementId), 'macarons')
|
||||||
myChart.showLoading()
|
myChart.showLoading()
|
||||||
|
|
||||||
let option = {
|
const option = {
|
||||||
title: {
|
title: {
|
||||||
text: 'Proxies',
|
text: 'Proxies',
|
||||||
subtext: 'now',
|
subtext: 'now',
|
||||||
@ -81,29 +86,37 @@ function DrawProxyChart(elementId, serverInfo) {
|
|||||||
return v.data.value
|
return v.data.value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
series: [{
|
series: [
|
||||||
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
radius: '55%',
|
radius: '55%',
|
||||||
center: ['50%', '60%'],
|
center: ['50%', '60%'],
|
||||||
data: [{
|
data: [
|
||||||
|
{
|
||||||
value: serverInfo.proxy_type_count.tcp,
|
value: serverInfo.proxy_type_count.tcp,
|
||||||
name: 'TCP'
|
name: 'TCP'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
value: serverInfo.proxy_type_count.udp,
|
value: serverInfo.proxy_type_count.udp,
|
||||||
name: 'UDP'
|
name: 'UDP'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
value: serverInfo.proxy_type_count.http,
|
value: serverInfo.proxy_type_count.http,
|
||||||
name: 'HTTP'
|
name: 'HTTP'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
value: serverInfo.proxy_type_count.https,
|
value: serverInfo.proxy_type_count.https,
|
||||||
name: 'HTTPS'
|
name: 'HTTPS'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
value: serverInfo.proxy_type_count.stcp,
|
value: serverInfo.proxy_type_count.stcp,
|
||||||
name: 'STCP'
|
name: 'STCP'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
value: serverInfo.proxy_type_count.xtcp,
|
value: serverInfo.proxy_type_count.xtcp,
|
||||||
name: 'XTCP'
|
name: 'XTCP'
|
||||||
}],
|
}
|
||||||
|
],
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
emphasis: {
|
emphasis: {
|
||||||
shadowBlur: 10,
|
shadowBlur: 10,
|
||||||
@ -111,33 +124,34 @@ function DrawProxyChart(elementId, serverInfo) {
|
|||||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
};
|
]
|
||||||
myChart.setOption(option);
|
}
|
||||||
|
myChart.setOption(option)
|
||||||
myChart.hideLoading()
|
myChart.hideLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7 days
|
// 7 days
|
||||||
function DrawProxyTrafficChart(elementId, trafficInArr, trafficOutArr) {
|
function DrawProxyTrafficChart(elementId, trafficInArr, trafficOutArr) {
|
||||||
let params = {
|
const params = {
|
||||||
width: '600px',
|
width: '600px',
|
||||||
height: '400px'
|
height: '400px'
|
||||||
}
|
}
|
||||||
|
|
||||||
let myChart = echarts.init(document.getElementById(elementId), 'macarons', params);
|
const myChart = echarts.init(document.getElementById(elementId), 'macarons', params)
|
||||||
myChart.showLoading()
|
myChart.showLoading()
|
||||||
|
|
||||||
trafficInArr = trafficInArr.reverse()
|
trafficInArr = trafficInArr.reverse()
|
||||||
trafficOutArr = trafficOutArr.reverse()
|
trafficOutArr = trafficOutArr.reverse()
|
||||||
let now = new Date()
|
let now = new Date()
|
||||||
now = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6)
|
now = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6)
|
||||||
let dates = new Array()
|
const dates = []
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
dates.push(now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate())
|
dates.push(now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate())
|
||||||
now = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1)
|
now = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
let option = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
@ -148,9 +162,9 @@ function DrawProxyTrafficChart(elementId, trafficInArr, trafficOutArr) {
|
|||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
html += data[0].name + '<br/>'
|
html += data[0].name + '<br/>'
|
||||||
}
|
}
|
||||||
for (let v of data) {
|
for (const v of data) {
|
||||||
let colorEl = '<span style="display:inline-block;margin-right:5px;' +
|
const colorEl =
|
||||||
'border-radius:10px;width:9px;height:9px;background-color:' + v.color + '"></span>';
|
'<span style="display:inline-block;margin-right:5px;' + 'border-radius:10px;width:9px;height:9px;background-color:' + v.color + '"></span>'
|
||||||
html += colorEl + v.seriesName + ': ' + Humanize.fileSize(v.value) + '<br/>'
|
html += colorEl + v.seriesName + ': ' + Humanize.fileSize(v.value) + '<br/>'
|
||||||
}
|
}
|
||||||
return html
|
return html
|
||||||
@ -165,35 +179,37 @@ function DrawProxyTrafficChart(elementId, trafficInArr, trafficOutArr) {
|
|||||||
bottom: '3%',
|
bottom: '3%',
|
||||||
containLabel: true
|
containLabel: true
|
||||||
},
|
},
|
||||||
xAxis: [{
|
xAxis: [
|
||||||
|
{
|
||||||
type: 'category',
|
type: 'category',
|
||||||
data: dates
|
data: dates
|
||||||
}],
|
}
|
||||||
yAxis: [{
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
type: 'value',
|
type: 'value',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: function(value) {
|
formatter: function(value) {
|
||||||
return Humanize.fileSize(value)
|
return Humanize.fileSize(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}],
|
}
|
||||||
series: [{
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
name: 'Traffic In',
|
name: 'Traffic In',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: trafficInArr
|
data: trafficInArr
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
name: 'Traffic Out',
|
name: 'Traffic Out',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: trafficOutArr
|
data: trafficOutArr
|
||||||
}]
|
}
|
||||||
};
|
]
|
||||||
myChart.setOption(option);
|
}
|
||||||
|
myChart.setOption(option)
|
||||||
myChart.hideLoading()
|
myChart.hideLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { DrawTrafficChart, DrawProxyChart, DrawProxyTrafficChart }
|
||||||
DrawTrafficChart,
|
|
||||||
DrawProxyChart,
|
|
||||||
DrawProxyTrafficChart
|
|
||||||
}
|
|
||||||
|
20
web/frps/src/utils/fetch.js
Normal file
20
web/frps/src/utils/fetch.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Message } from 'element-ui'
|
||||||
|
|
||||||
|
export default function(api, init = {}) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
fetch(`/api/${api}`, Object.assign({ credentials: 'include' }, init))
|
||||||
|
.then(res => {
|
||||||
|
if (res.status < 200 || res.status >= 300) {
|
||||||
|
Message.warning('Get server info from frps failed!')
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(res ? res.json() : undefined)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.$message.error(err.message)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
@ -5,8 +5,8 @@ class BaseProxy {
|
|||||||
this.encryption = proxyStats.conf.use_encryption
|
this.encryption = proxyStats.conf.use_encryption
|
||||||
this.compression = proxyStats.conf.use_compression
|
this.compression = proxyStats.conf.use_compression
|
||||||
} else {
|
} else {
|
||||||
this.encryption = ""
|
this.encryption = ''
|
||||||
this.compression = ""
|
this.compression = ''
|
||||||
}
|
}
|
||||||
this.conns = proxyStats.cur_conns
|
this.conns = proxyStats.cur_conns
|
||||||
this.traffic_in = proxyStats.today_traffic_in
|
this.traffic_in = proxyStats.today_traffic_in
|
||||||
@ -20,13 +20,13 @@ class BaseProxy {
|
|||||||
class TcpProxy extends BaseProxy {
|
class TcpProxy extends BaseProxy {
|
||||||
constructor(proxyStats) {
|
constructor(proxyStats) {
|
||||||
super(proxyStats)
|
super(proxyStats)
|
||||||
this.type = "tcp"
|
this.type = 'tcp'
|
||||||
if (proxyStats.conf != null) {
|
if (proxyStats.conf != null) {
|
||||||
this.addr = ":" + proxyStats.conf.remote_port
|
this.addr = ':' + proxyStats.conf.remote_port
|
||||||
this.port = proxyStats.conf.remote_port
|
this.port = proxyStats.conf.remote_port
|
||||||
} else {
|
} else {
|
||||||
this.addr = ""
|
this.addr = ''
|
||||||
this.port = ""
|
this.port = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,13 +34,13 @@ class TcpProxy extends BaseProxy {
|
|||||||
class UdpProxy extends BaseProxy {
|
class UdpProxy extends BaseProxy {
|
||||||
constructor(proxyStats) {
|
constructor(proxyStats) {
|
||||||
super(proxyStats)
|
super(proxyStats)
|
||||||
this.type = "udp"
|
this.type = 'udp'
|
||||||
if (proxyStats.conf != null) {
|
if (proxyStats.conf != null) {
|
||||||
this.addr = ":" + proxyStats.conf.remote_port
|
this.addr = ':' + proxyStats.conf.remote_port
|
||||||
this.port = proxyStats.conf.remote_port
|
this.port = proxyStats.conf.remote_port
|
||||||
} else {
|
} else {
|
||||||
this.addr = ""
|
this.addr = ''
|
||||||
this.port = ""
|
this.port = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,22 +48,22 @@ class UdpProxy extends BaseProxy {
|
|||||||
class HttpProxy extends BaseProxy {
|
class HttpProxy extends BaseProxy {
|
||||||
constructor(proxyStats, port, subdomain_host) {
|
constructor(proxyStats, port, subdomain_host) {
|
||||||
super(proxyStats)
|
super(proxyStats)
|
||||||
this.type = "http"
|
this.type = 'http'
|
||||||
this.port = port
|
this.port = port
|
||||||
if (proxyStats.conf != null) {
|
if (proxyStats.conf != null) {
|
||||||
this.custom_domains = proxyStats.conf.custom_domains
|
this.custom_domains = proxyStats.conf.custom_domains
|
||||||
this.host_header_rewrite = proxyStats.conf.host_header_rewrite
|
this.host_header_rewrite = proxyStats.conf.host_header_rewrite
|
||||||
this.locations = proxyStats.conf.locations
|
this.locations = proxyStats.conf.locations
|
||||||
if (proxyStats.conf.sub_domain != "") {
|
if (proxyStats.conf.sub_domain !== '') {
|
||||||
this.subdomain = proxyStats.conf.sub_domain + "." + subdomain_host
|
this.subdomain = proxyStats.conf.sub_domain + '.' + subdomain_host
|
||||||
} else {
|
} else {
|
||||||
this.subdomain = ""
|
this.subdomain = ''
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.custom_domains = ""
|
this.custom_domains = ''
|
||||||
this.host_header_rewrite = ""
|
this.host_header_rewrite = ''
|
||||||
this.subdomain = ""
|
this.subdomain = ''
|
||||||
this.locations = ""
|
this.locations = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,18 +71,18 @@ class HttpProxy extends BaseProxy {
|
|||||||
class HttpsProxy extends BaseProxy {
|
class HttpsProxy extends BaseProxy {
|
||||||
constructor(proxyStats, port, subdomain_host) {
|
constructor(proxyStats, port, subdomain_host) {
|
||||||
super(proxyStats)
|
super(proxyStats)
|
||||||
this.type = "https"
|
this.type = 'https'
|
||||||
this.port = port
|
this.port = port
|
||||||
if (proxyStats.conf != null) {
|
if (proxyStats.conf != null) {
|
||||||
this.custom_domains = proxyStats.conf.custom_domains
|
this.custom_domains = proxyStats.conf.custom_domains
|
||||||
if (proxyStats.conf.sub_domain != "") {
|
if (proxyStats.conf.sub_domain !== '') {
|
||||||
this.subdomain = proxyStats.conf.sub_domain + "." + subdomain_host
|
this.subdomain = proxyStats.conf.sub_domain + '.' + subdomain_host
|
||||||
} else {
|
} else {
|
||||||
this.subdomain = ""
|
this.subdomain = ''
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.custom_domains = ""
|
this.custom_domains = ''
|
||||||
this.subdomain = ""
|
this.subdomain = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ class HttpsProxy extends BaseProxy {
|
|||||||
class StcpProxy extends BaseProxy {
|
class StcpProxy extends BaseProxy {
|
||||||
constructor(proxyStats) {
|
constructor(proxyStats) {
|
||||||
super(proxyStats)
|
super(proxyStats)
|
||||||
this.type = "stcp"
|
this.type = 'stcp'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
web/frps/vue.config.js
Normal file
16
web/frps/vue.config.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module.exports = {
|
||||||
|
publicPath: './',
|
||||||
|
devServer: {
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: 8010,
|
||||||
|
proxy: {
|
||||||
|
'/api/': {
|
||||||
|
target: 'http://127.0.0.1:8080/api',
|
||||||
|
changeOrigin: true,
|
||||||
|
pathRewrite: {
|
||||||
|
'^/api': ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,107 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
var webpack = require('webpack')
|
|
||||||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
|
||||||
var VueLoaderPlugin = require('vue-loader/lib/plugin')
|
|
||||||
var url = require('url')
|
|
||||||
var publicPath = ''
|
|
||||||
|
|
||||||
module.exports = (options = {}) => ({
|
|
||||||
entry: {
|
|
||||||
vendor: './src/main'
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: path.resolve(__dirname, 'dist'),
|
|
||||||
filename: options.dev ? '[name].js' : '[name].js?[chunkhash]',
|
|
||||||
chunkFilename: '[id].js?[chunkhash]',
|
|
||||||
publicPath: options.dev ? '/assets/' : publicPath
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.js', '.vue', '.json'],
|
|
||||||
alias: {
|
|
||||||
'vue$': 'vue/dist/vue.esm.js',
|
|
||||||
'@': path.resolve(__dirname, 'src'),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [{
|
|
||||||
test: /\.vue$/,
|
|
||||||
loader: 'vue-loader'
|
|
||||||
}, {
|
|
||||||
test: /\.js$/,
|
|
||||||
use: ['babel-loader'],
|
|
||||||
exclude: /node_modules/
|
|
||||||
}, {
|
|
||||||
test: /\.html$/,
|
|
||||||
use: [{
|
|
||||||
loader: 'html-loader',
|
|
||||||
options: {
|
|
||||||
root: path.resolve(__dirname, 'src'),
|
|
||||||
attrs: ['img:src', 'link:href']
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}, {
|
|
||||||
test: /\.less$/,
|
|
||||||
loader: 'style-loader!css-loader!postcss-loader!less-loader'
|
|
||||||
}, {
|
|
||||||
test: /\.css$/,
|
|
||||||
use: ['style-loader', 'css-loader', 'postcss-loader']
|
|
||||||
}, {
|
|
||||||
test: /favicon\.png$/,
|
|
||||||
use: [{
|
|
||||||
loader: 'file-loader',
|
|
||||||
options: {
|
|
||||||
name: '[name].[ext]?[hash]'
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}, {
|
|
||||||
test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
|
|
||||||
exclude: /favicon\.png$/,
|
|
||||||
use: [{
|
|
||||||
loader: 'url-loader',
|
|
||||||
options: {
|
|
||||||
limit: 10000
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.optimize.CommonsChunkPlugin({
|
|
||||||
names: ['vendor', 'manifest']
|
|
||||||
}),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
favicon: 'src/assets/favicon.ico',
|
|
||||||
template: 'src/index.html'
|
|
||||||
}),
|
|
||||||
new webpack.NormalModuleReplacementPlugin(/element-ui[\/\\]lib[\/\\]locale[\/\\]lang[\/\\]zh-CN/, 'element-ui/lib/locale/lang/en'),
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'process.env': {
|
|
||||||
NODE_ENV: '"production"'
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new webpack.optimize.UglifyJsPlugin({
|
|
||||||
sourceMap: false,
|
|
||||||
comments: false,
|
|
||||||
compress: {
|
|
||||||
warnings: false
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
new VueLoaderPlugin()
|
|
||||||
],
|
|
||||||
devServer: {
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 8010,
|
|
||||||
proxy: {
|
|
||||||
'/api/': {
|
|
||||||
target: 'http://127.0.0.1:8080',
|
|
||||||
changeOrigin: true,
|
|
||||||
pathRewrite: {
|
|
||||||
'^/api': ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
historyApiFallback: {
|
|
||||||
index: url.parse(options.dev ? '/assets/' : publicPath).pathname
|
|
||||||
}
|
|
||||||
}//,
|
|
||||||
//devtool: options.dev ? '#eval-source-map' : '#source-map'
|
|
||||||
})
|
|
9478
web/frps/yarn.lock
Normal file
9478
web/frps/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user