Merge pull request #2206 from fatedier/dev

bump version
This commit is contained in:
fatedier 2021-01-19 20:56:06 +08:00 committed by GitHub
commit b2ae433e18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 11034 additions and 10630 deletions

25
.circleci/config.yml Normal file
View 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

View File

@ -27,4 +27,4 @@ jobs:
version: latest
args: release --rm-dist --release-notes=./Release.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GPR_TOKEN }}

View File

@ -1,12 +0,0 @@
sudo: false
language: go
go:
- 1.14.x
- 1.15.x
install:
- make
script:
- make alltest

View File

@ -1,6 +1,6 @@
# frp
[![Build Status](https://travis-ci.org/fatedier/frp.svg?branch=master)](https://travis-ci.org/fatedier/frp)
[![Build Status](https://circleci.com/gh/fatedier/frp.svg?style=shield)](https://circleci.com/gh/fatedier/frp)
[![GitHub release](https://img.shields.io/github/tag/fatedier/frp.svg?label=release)](https://github.com/fatedier/frp/releases)
[README](README.md) | [中文文档](README_zh.md)

View File

@ -1,3 +1,8 @@
### 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

View 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%}

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@ -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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -17,10 +17,10 @@ package client
import (
"context"
"crypto/tls"
"fmt"
"io"
"net"
"runtime/debug"
"strconv"
"sync"
"time"
@ -222,8 +222,10 @@ func (ctl *Control) connectServer() (conn net.Conn, err error) {
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 {
xl.Warn("start new connection to server error: %v", err)
return
@ -295,7 +297,7 @@ func (ctl *Control) msgHandler() {
}()
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()
hbCheck := time.NewTicker(time.Second)
defer hbCheck.Stop()
@ -314,7 +316,7 @@ func (ctl *Control) msgHandler() {
}
ctl.sendCh <- pingMsg
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")
// let reader() stop
ctl.conn.Close()

View File

@ -21,6 +21,7 @@ import (
"io/ioutil"
"net"
"runtime"
"strconv"
"sync"
"sync/atomic"
"time"
@ -215,8 +216,9 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
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 {
return
}

View File

@ -157,17 +157,16 @@ func parseClientCommonCfgFromIni(content string) (config.ClientCommonConf, error
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
cfg = config.GetDefaultClientConf()
strs := strings.Split(serverAddr, ":")
if len(strs) < 2 {
err = fmt.Errorf("invalid server_addr")
ipStr, portStr, err := net.SplitHostPort(serverAddr)
if err != nil {
err = fmt.Errorf("invalid server_addr: %v", err)
return
}
if strs[0] != "" {
cfg.ServerAddr = strs[0]
}
cfg.ServerPort, err = strconv.Atoi(strs[1])
cfg.ServerAddr = ipStr
cfg.ServerPort, err = strconv.Atoi(portStr)
if err != nil {
err = fmt.Errorf("invalid server_addr")
err = fmt.Errorf("invalid server_addr: %v", err)
return
}

View File

@ -105,6 +105,7 @@ var rootCmd = &cobra.Command{
var cfg config.ServerCommonConf
var err error
if cfgFile != "" {
log.Info("frps uses config file: %s", cfgFile)
var content string
content, err = config.GetRenderedConfFromFile(cfgFile)
if err != nil {
@ -112,6 +113,7 @@ var rootCmd = &cobra.Command{
}
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
} else {
log.Info("frps uses command line arguments for config")
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
}
if err != nil {
@ -212,7 +214,7 @@ func runServer(cfg config.ServerCommonConf) (err error) {
if err != nil {
return err
}
log.Info("start frps success")
log.Info("frps started successfully")
svr.Run()
return
}

View File

@ -23,15 +23,30 @@ log_max_days = 3
disable_log_color = false
# 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
# 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
# auth token
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
admin_addr = 127.0.0.1
admin_port = 7400

View File

@ -23,7 +23,7 @@ vhost_https_port = 443
# response header timeout(seconds) for vhost http server, default is 60s
# 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
# 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.
@ -44,6 +44,7 @@ enable_prometheus = true
# dashboard assets directory(only for debug mode)
# assets_dir = ./static
# console or real logFile path like ./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.
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 "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "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
# 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
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 "".
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 "".
oidc_client_secret =
oidc_audience =
# OidcAudience specifies the audience of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
oidc_audience =
# oidc_skip_expiry_check specifies whether to skip checking if the OIDC token is expired.
# By default, this value is false.
oidc_skip_expiry_check = false
# OidcTokenEndpointUrl 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 =
# 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
# the default value of heartbeat_timeout is 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
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_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_cert_file = server.crt

View File

@ -209,9 +209,10 @@ path = /handler
ops = NewProxy
```
addr: the address where the external RPC service listens on.
path: http request url path for the POST request.
ops: operations plugin needs to handle (e.g. "Login", "NewProxy", ...).
- 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.
- 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

View File

@ -121,11 +121,11 @@ type ClientCommonConf struct {
// HeartBeatInterval specifies at what interval heartbeats are sent to the
// server, in seconds. It is not recommended to change this value. By
// default, this value is 30.
HeartBeatInterval int64 `json:"heartbeat_interval"`
HeartbeatInterval int64 `json:"heartbeat_interval"`
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
// before the connection is terminated, in seconds. It is not recommended
// to change this value. By default, this value is 90.
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
HeartbeatTimeout int64 `json:"heartbeat_timeout"`
// Client meta info
Metas map[string]string `json:"metas"`
// UDPPacketSize specifies the udp packet size
@ -160,8 +160,8 @@ func GetDefaultClientConf() ClientCommonConf {
TLSCertFile: "",
TLSKeyFile: "",
TLSTrustedCaFile: "",
HeartBeatInterval: 30,
HeartBeatTimeout: 90,
HeartbeatInterval: 30,
HeartbeatTimeout: 90,
Metas: make(map[string]string),
UDPPacketSize: 1500,
}
@ -312,7 +312,7 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
return
}
cfg.HeartBeatTimeout = v
cfg.HeartbeatTimeout = v
}
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")
return
}
cfg.HeartBeatInterval = v
cfg.HeartbeatInterval = v
}
for k, v := range conf.Section("common") {
if strings.HasPrefix(k, "meta_") {
@ -338,12 +338,12 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, 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")
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")
return
}

View File

@ -83,7 +83,7 @@ type ServerCommonConf struct {
// AssetsDir specifies the local directory that the dashboard will load
// resources from. If this value is "", assets will be loaded from the
// 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
// only be used if LogWay is set appropriately. By default, this value is
// "console".
@ -154,7 +154,7 @@ type ServerCommonConf struct {
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
// before terminating the connection. It is not recommended to change this
// 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
// connection. By default, this value is 10.
UserConnTimeout int64 `json:"user_conn_timeout"`
@ -199,7 +199,7 @@ func GetDefaultServerConf() ServerCommonConf {
TLSCertFile: "",
TLSKeyFile: "",
TLSTrustedCaFile: "",
HeartBeatTimeout: 90,
HeartbeatTimeout: 90,
UserConnTimeout: 10,
Custom404Page: "",
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")
return
}
cfg.HeartBeatTimeout = v
cfg.HeartbeatTimeout = v
}
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 {
if strings.HasPrefix(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{
Name: name,
Addr: section["addr"],
Path: section["path"],
Ops: strings.Split(section["ops"], ","),
Name: name,
Addr: section["addr"],
Path: section["path"],
Ops: strings.Split(section["ops"], ","),
TLSVerify: tls_verify,
}
for i := range options.Ops {
options.Ops[i] = strings.TrimSpace(options.Ops[i])

View File

@ -17,19 +17,22 @@ package plugin
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strings"
)
type HTTPPluginOptions struct {
Name string
Addr string
Path string
Ops []string
Name string
Addr string
Path string
Ops []string
TLSVerify bool
}
type httpPlugin struct {
@ -40,10 +43,25 @@ type httpPlugin struct {
}
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{
options: options,
url: fmt.Sprintf("http://%s%s", options.Addr, options.Path),
client: &http.Client{},
url: url,
client: client,
}
}

View File

@ -15,6 +15,7 @@
package util
import (
"net"
"net/http"
"strings"
)
@ -33,6 +34,7 @@ func OkResponse() *http.Response {
return res
}
// TODO: use "CanonicalHost" func to replace all "GetHostFromAddr" func.
func GetHostFromAddr(addr string) (host string) {
strs := strings.Split(addr, ":")
if len(strs) > 1 {
@ -42,3 +44,34 @@ func GetHostFromAddr(addr string) (host string) {
}
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, "]:")
}

View File

@ -19,7 +19,7 @@ import (
"strings"
)
var version string = "0.34.3"
var version string = "0.35.0"
func Full() string {
return version

View File

@ -17,6 +17,7 @@ package vhost
import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"log"
@ -59,20 +60,25 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
req.URL.Scheme = "http"
url := req.Context().Value(RouteInfoURL).(string)
oldHost := util.GetHostFromAddr(req.Context().Value(RouteInfoHost).(string))
host := rp.GetRealHost(oldHost, url)
if host != "" {
req.Host = host
}
req.URL.Host = req.Host
rc := rp.GetRouteConfig(oldHost, url)
if rc != nil {
if rc.RewriteHost != "" {
req.Host = rc.RewriteHost
}
// 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 headers {
req.Header.Set(k, v)
for k, v := range rc.Headers {
req.Header.Set(k, v)
}
} else {
req.URL.Host = req.Host
}
},
Transport: &http.Transport{
ResponseHeaderTimeout: rp.responseHeaderTimeout,
DisableKeepAlives: true,
IdleConnTimeout: 60 * time.Second,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
url := ctx.Value(RouteInfoURL).(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)
}
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) {
vr, ok := rp.getVhost(domain, location)
if ok {

View File

@ -144,7 +144,7 @@ func (v *Muxer) handle(c net.Conn) {
sConn, reqInfoMap, err := v.vhostFunc(c)
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()
return
}

View File

@ -408,7 +408,7 @@ func (ctl *Control) manager() {
for {
select {
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")
return
}

View File

@ -74,7 +74,7 @@ func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) {
SubdomainHost: svr.cfg.SubDomainHost,
MaxPoolCount: svr.cfg.MaxPoolCount,
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
HeartBeatTimeout: svr.cfg.HeartBeatTimeout,
HeartBeatTimeout: svr.cfg.HeartbeatTimeout,
TotalTrafficIn: serverStats.TotalTrafficIn,
TotalTrafficOut: serverStats.TotalTrafficOut,

View File

@ -100,7 +100,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn,
xl.Warn("failed to get work connection: %v", err)
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())
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")
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)
}
}(listener)

View File

@ -139,6 +139,7 @@ func TestHealthCheck(t *testing.T) {
}
httpSvc3 := mock.NewHTTPServer(15005, func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Second)
w.Write([]byte("http3"))
})
err = httpSvc3.Start()
@ -147,6 +148,7 @@ func TestHealthCheck(t *testing.T) {
}
httpSvc4 := mock.NewHTTPServer(15006, func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Second)
w.Write([]byte("http4"))
})
err = httpSvc4.Start()
@ -277,16 +279,30 @@ func TestHealthCheck(t *testing.T) {
// ****** load balancing type http ******
result = make([]string, 0)
var wait sync.WaitGroup
var mu sync.Mutex
wait.Add(2)
code, body, _, err = util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
assert.NoError(err)
assert.Equal(200, code)
result = append(result, body)
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()
}()
code, body, _, err = util.SendHTTPMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
assert.NoError(err)
assert.Equal(200, code)
result = append(result, body)
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()
}()
wait.Wait()
assert.Contains(result, "http3")
assert.Contains(result, "http4")

View File

@ -1,14 +0,0 @@
{
"presets": [
["es2015", { "modules": false }]
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}

5
web/frps/.editorconfig Normal file
View File

@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -0,0 +1,2 @@
# just a flag
ENV = 'development'

2
web/frps/.env.production Normal file
View File

@ -0,0 +1,2 @@
# just a flag
ENV = 'production'

267
web/frps/.eslintrc.js Normal file
View 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
View File

@ -1,6 +1,25 @@
.DS_Store
node_modules/
dist/
npm-debug.log
node_modules
/dist
# 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
.vscode/settings.json
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
package-lock.json

8
web/frps/.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"bracketSpacing": true,
"printWidth": 160,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"arrowParens": "avoid"
}

View File

@ -1,7 +1,10 @@
.PHONY: dist build
build:
build: install
@npm run build
dev: install
@npm run dev
@npm run serve
install:
@npm install

25
web/frps/README.md Normal file
View 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
View File

@ -0,0 +1,3 @@
module.exports = {
presets: ['@vue/cli-plugin-babel/preset']
}

File diff suppressed because it is too large Load Diff

View File

@ -4,45 +4,43 @@
"author": "fatedier",
"private": true,
"scripts": {
"dev": "webpack-dev-server -d --inline --hot --env.dev",
"build": "rimraf dist && webpack -p --progress --hide-modules"
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"bootstrap": "^3.3.7",
"echarts": "^3.5.0",
"element-ui": "^2.3.8",
"core-js": "^3.7.0",
"echarts": "^4.9.0",
"element-ui": "^2.14.1",
"humanize-plus": "^1.8.2",
"vue": "^2.5.16",
"vue-resource": "^1.2.1",
"vue-router": "^2.3.0",
"whatwg-fetch": "^2.0.3"
},
"engines": {
"node": ">=6"
"vue": "^2.6.12",
"vue-router": "^3.4.9",
"vuex": "^3.5.1",
"whatwg-fetch": "^3.5.0"
},
"devDependencies": {
"autoprefixer": "^6.6.0",
"babel-core": "^6.21.0",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.4.0",
"babel-plugin-component": "^1.1.1",
"babel-preset-es2015": "^6.13.2",
"css-loader": "^0.27.0",
"eslint": "^3.12.2",
"eslint-config-enough": "^0.2.2",
"eslint-loader": "^1.6.3",
"file-loader": "^0.10.1",
"html-loader": "^0.4.5",
"html-webpack-plugin": "^2.24.1",
"less": "^3.0.4",
"less-loader": "^4.1.0",
"postcss-loader": "^1.3.3",
"rimraf": "^2.5.4",
"style-loader": "^0.13.2",
"url-loader": "^1.0.1",
"vue-loader": "^15.0.10",
"vue-template-compiler": "^2.1.8",
"webpack": "^2.2.0-rc.4",
"webpack-dev-server": "^3.1.4"
}
"@vue/cli-plugin-babel": "~4.5.9",
"@vue/cli-plugin-eslint": "~4.5.9",
"@vue/cli-plugin-router": "~4.5.9",
"@vue/cli-plugin-vuex": "~4.5.9",
"@vue/cli-service": "~4.5.9",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"eslint": "^7.14.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.1.0",
"eslint-plugin-vue": "^7.1.0",
"less": "^3.12.2",
"less-loader": "^7.1.0",
"node-sass": "^5.0.0",
"sass-loader": "^10.1.0",
"vue-template-compiler": "^2.6.12"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

View File

@ -1,5 +0,0 @@
module.exports = {
plugins: [
require('autoprefixer')()
]
}

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View 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>

View File

@ -1,80 +1,87 @@
<template>
<div id="app">
<header class="grid-content header-color">
<el-row>
<a class="brand" href="#">frp</a>
</el-row>
</header>
<section>
<el-row :gutter="20">
<el-col id="side-nav" :xs="24" :md="4">
<el-menu default-active="1" mode="vertical" theme="light" router="false" @select="handleSelect">
<el-menu-item index="/">Overview</el-menu-item>
<el-submenu index="/proxies">
<template slot="title">Proxies</template>
<el-menu-item index="/proxies/tcp">TCP</el-menu-item>
<el-menu-item index="/proxies/udp">UDP</el-menu-item>
<el-menu-item index="/proxies/http">HTTP</el-menu-item>
<el-menu-item index="/proxies/https">HTTPS</el-menu-item>
<el-menu-item index="/proxies/stcp">STCP</el-menu-item>
</el-submenu>
<el-menu-item index="">Help</el-menu-item>
</el-menu>
</el-col>
<div id="app">
<header class="grid-content header-color">
<el-row>
<a class="brand" href="#">frp</a>
</el-row>
</header>
<section>
<el-row>
<el-col id="side-nav" :xs="24" :md="4">
<el-menu default-active="1" mode="vertical" theme="light" router @select="handleSelect">
<el-menu-item index="/">Overview</el-menu-item>
<el-submenu index="/proxies">
<template slot="title">Proxies</template>
<el-menu-item index="/proxies/tcp">TCP</el-menu-item>
<el-menu-item index="/proxies/udp">UDP</el-menu-item>
<el-menu-item index="/proxies/http">HTTP</el-menu-item>
<el-menu-item index="/proxies/https">HTTPS</el-menu-item>
<el-menu-item index="/proxies/stcp">STCP</el-menu-item>
</el-submenu>
<el-menu-item index="">Help</el-menu-item>
</el-menu>
</el-col>
<el-col :xs="24" :md="20">
<div id="content">
<router-view></router-view>
</div>
</el-col>
</el-row>
</section>
<footer></footer>
</div>
<el-col :xs="24" :md="20">
<div id="content">
<router-view v-if="serverInfo" />
</div>
</el-col>
</el-row>
</section>
</div>
</template>
<script>
export default {
methods: {
handleSelect(key, path) {
if (key == '') {
window.open("https://github.com/fatedier/frp")
}
}
}
export default {
computed: {
serverInfo() {
return this.$store.state.serverInfo
}
},
async created() {
this.$store.dispatch('fetchServerInfo')
},
methods: {
handleSelect(key, path) {
if (key === '') {
window.open('https://github.com/fatedier/frp')
}
}
}
}
</script>
<style>
body {
background-color: #fafafa;
margin: 0px;
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;
}
body {
background-color: #fafafa;
margin: 0px;
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;
}
</style>

View File

@ -1,166 +1,160 @@
<template>
<div>
<el-row>
<el-col :md="12">
<div class="source">
<el-form label-position="left" class="server_info">
<el-form-item label="Version">
<span>{{ version }}</span>
</el-form-item>
<el-form-item label="BindPort">
<span>{{ bind_port }}</span>
</el-form-item>
<el-form-item label="BindUdpPort">
<span>{{ bind_udp_port }}</span>
</el-form-item>
<el-form-item label="Http Port">
<span>{{ vhost_http_port }}</span>
</el-form-item>
<el-form-item label="Https Port">
<span>{{ vhost_https_port }}</span>
</el-form-item>
<el-form-item label="Subdomain Host">
<span>{{ subdomain_host }}</span>
</el-form-item>
<el-form-item label="Max PoolCount">
<span>{{ max_pool_count }}</span>
</el-form-item>
<el-form-item label="Max Ports Per Client">
<span>{{ max_ports_per_client }}</span>
</el-form-item>
<el-form-item label="HeartBeat Timeout">
<span>{{ heart_beat_timeout }}</span>
</el-form-item>
<el-form-item label="Client Counts">
<span>{{ client_counts }}</span>
</el-form-item>
<el-form-item label="Current Connections">
<span>{{ cur_conns }}</span>
</el-form-item>
<el-form-item label="Proxy Counts">
<span>{{ proxy_counts }}</span>
</el-form-item>
</el-form>
</div>
</el-col>
<el-col :md="12">
<div id="traffic" style="width: 400px;height:250px;margin-bottom: 30px;"></div>
<div id="proxies" style="width: 400px;height:250px;"></div>
</el-col>
</el-row>
</div>
<div>
<el-row>
<el-col :md="12">
<div class="source">
<el-form label-position="left" class="server_info">
<el-form-item label="Version">
<span>{{ version }}</span>
</el-form-item>
<el-form-item label="BindPort">
<span>{{ bind_port }}</span>
</el-form-item>
<el-form-item label="BindUdpPort">
<span>{{ bind_udp_port }}</span>
</el-form-item>
<el-form-item label="Http Port">
<span>{{ vhost_http_port }}</span>
</el-form-item>
<el-form-item label="Https Port">
<span>{{ vhost_https_port }}</span>
</el-form-item>
<el-form-item label="Subdomain Host">
<span>{{ subdomain_host }}</span>
</el-form-item>
<el-form-item label="Max PoolCount">
<span>{{ max_pool_count }}</span>
</el-form-item>
<el-form-item label="Max Ports Per Client">
<span>{{ max_ports_per_client }}</span>
</el-form-item>
<el-form-item label="HeartBeat Timeout">
<span>{{ heart_beat_timeout }}</span>
</el-form-item>
<el-form-item label="Client Counts">
<span>{{ client_counts }}</span>
</el-form-item>
<el-form-item label="Current Connections">
<span>{{ cur_conns }}</span>
</el-form-item>
<el-form-item label="Proxy Counts">
<span>{{ proxy_counts }}</span>
</el-form-item>
</el-form>
</div>
</el-col>
<el-col :md="12">
<div id="traffic" style="width: 400px; height: 250px; margin-bottom: 30px" />
<div id="proxies" style="width: 400px; height: 250px" />
</el-col>
</el-row>
</div>
</template>
<script>
import {DrawTrafficChart, DrawProxyChart} from '../utils/chart.js'
export default {
data() {
return {
version: '',
bind_port: '',
bind_udp_port: '',
vhost_http_port: '',
vhost_https_port: '',
subdomain_host: '',
max_pool_count: '',
max_ports_per_client: '',
heart_beat_timeout: '',
client_counts: '',
cur_conns: '',
proxy_counts: ''
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
fetchData() {
fetch('/api/serverinfo', {credentials: 'include'})
.then(res => {
return res.json()
}).then(json => {
this.version = json.version
this.bind_port = json.bind_port
this.bind_udp_port = json.bind_udp_port
if (this.bind_udp_port == 0) {
this.bind_udp_port = "disable"
}
this.vhost_http_port = json.vhost_http_port
if (this.vhost_http_port == 0) {
this.vhost_http_port = "disable"
}
this.vhost_https_port = json.vhost_https_port
if (this.vhost_https_port == 0) {
this.vhost_https_port = "disable"
}
this.subdomain_host = json.subdomain_host
this.max_pool_count = json.max_pool_count
this.max_ports_per_client = json.max_ports_per_client
if (this.max_ports_per_client == 0) {
this.max_ports_per_client = "no limit"
}
this.heart_beat_timeout = json.heart_beat_timeout
this.client_counts = json.client_counts
this.cur_conns = json.cur_conns
this.proxy_counts = 0
if (json.proxy_type_count != null) {
if (json.proxy_type_count.tcp != null) {
this.proxy_counts += json.proxy_type_count.tcp
}
if (json.proxy_type_count.udp != null) {
this.proxy_counts += json.proxy_type_count.udp
}
if (json.proxy_type_count.http != null) {
this.proxy_counts += json.proxy_type_count.http
}
if (json.proxy_type_count.https != null) {
this.proxy_counts += json.proxy_type_count.https
}
if (json.proxy_type_count.stcp != null) {
this.proxy_counts += json.proxy_type_count.stcp
}
if (json.proxy_type_count.xtcp != null) {
this.proxy_counts += json.proxy_type_count.xtcp
}
}
DrawTrafficChart('traffic', json.total_traffic_in, json.total_traffic_out)
DrawProxyChart('proxies', json)
}).catch( err => {
this.$message({
showClose: true,
message: 'Get server info from frps failed!',
type: 'warning'
})
})
}
}
import { DrawTrafficChart, DrawProxyChart } from '../utils/chart.js'
export default {
data() {
return {
version: '',
bind_port: '',
bind_udp_port: '',
vhost_http_port: '',
vhost_https_port: '',
subdomain_host: '',
max_pool_count: '',
max_ports_per_client: '',
heart_beat_timeout: '',
client_counts: '',
cur_conns: '',
proxy_counts: ''
}
},
computed: {
serverInfo() {
return this.$store.state.serverInfo
}
},
mounted() {
this.initData()
},
methods: {
initData() {
console.log(!!this.serverInfo, this.serverInfo)
if (!this.serverInfo) return
this.version = this.serverInfo.version
this.bind_port = this.serverInfo.bind_port
this.bind_udp_port = this.serverInfo.bind_udp_port
if (this.bind_udp_port === 0) {
this.bind_udp_port = 'disable'
}
this.vhost_http_port = this.serverInfo.vhost_http_port
if (this.vhost_http_port === 0) {
this.vhost_http_port = 'disable'
}
this.vhost_https_port = this.serverInfo.vhost_https_port
if (this.vhost_https_port === 0) {
this.vhost_https_port = 'disable'
}
this.subdomain_host = this.serverInfo.subdomain_host
this.max_pool_count = this.serverInfo.max_pool_count
this.max_ports_per_client = this.serverInfo.max_ports_per_client
if (this.max_ports_per_client === 0) {
this.max_ports_per_client = 'no limit'
}
this.heart_beat_timeout = this.serverInfo.heart_beat_timeout
this.client_counts = this.serverInfo.client_counts
this.cur_conns = this.serverInfo.cur_conns
this.proxy_counts = 0
if (this.serverInfo.proxy_type_count != null) {
if (this.serverInfo.proxy_type_count.tcp != null) {
this.proxy_counts += this.serverInfo.proxy_type_count.tcp
}
if (this.serverInfo.proxy_type_count.udp != null) {
this.proxy_counts += this.serverInfo.proxy_type_count.udp
}
if (this.serverInfo.proxy_type_count.http != null) {
this.proxy_counts += this.serverInfo.proxy_type_count.http
}
if (this.serverInfo.proxy_type_count.https != null) {
this.proxy_counts += this.serverInfo.proxy_type_count.https
}
if (this.serverInfo.proxy_type_count.stcp != null) {
this.proxy_counts += this.serverInfo.proxy_type_count.stcp
}
if (this.serverInfo.proxy_type_count.xtcp != null) {
this.proxy_counts += this.serverInfo.proxy_type_count.xtcp
}
}
DrawTrafficChart('traffic', this.serverInfo.total_traffic_in, this.serverInfo.total_traffic_out)
DrawProxyChart('proxies', this.serverInfo)
}
}
}
</script>
<style>
.source {
border: 1px solid #eaeefb;
border-radius: 4px;
transition: .2s;
padding: 24px;
border: 1px solid #eaeefb;
border-radius: 4px;
transition: 0.2s;
padding: 24px;
}
.server_info {
margin-left: 40px;
font-size: 0px;
margin-left: 40px;
font-size: 0px;
}
.server_info label {
width: 150px;
color: #99a9bf;
width: 150px;
color: #99a9bf;
}
.server_info .el-form-item {
margin-right: 0;
margin-bottom: 0;
width: 100%;
margin-right: 0;
margin-bottom: 0;
width: 100%;
}
</style>

View File

@ -1,19 +1,14 @@
<template>
<div>
<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">
<template slot-scope="props">
<el-popover
ref="popover4"
placement="right"
width="600"
style="margin-left:0px"
trigger="click">
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
<el-popover ref="popover4" placement="right" width="600" style="margin-left: 0px" trigger="click">
<my-traffic-chart :proxy-name="props.row.name" />
</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>
<el-form label-position="left" inline class="demo-table-expand">
<el-form-item label="Name">
<span>{{ props.row.name }}</span>
@ -45,104 +40,68 @@
<el-form-item label="Last Close">
<span>{{ props.row.last_close_time }}</span>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column
label="Name"
prop="name"
sortable>
</el-table-column>
<el-table-column
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">
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</el-form>
</template>
</el-table-column>
<el-table-column label="Name" prop="name" sortable />
<el-table-column label="Port" prop="port" sortable />
<el-table-column label="Connections" prop="conns" sortable />
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
<el-table-column label="status" prop="status" sortable>
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import Humanize from 'humanize-plus';
import Traffic from './Traffic.vue'
import {
HttpProxy
} from '../utils/proxy.js'
export default {
data() {
return {
proxies: null,
vhost_http_port: "",
subdomain_host: ""
import Humanize from 'humanize-plus'
import Traffic from './Traffic.vue'
import { HttpProxy } from '../utils/proxy.js'
export default {
components: {
'my-traffic-chart': Traffic
},
data() {
return {
proxies: [],
vhost_http_port: '',
subdomain_host: ''
}
},
computed: {
serverInfo() {
return this.$store.state.serverInfo
}
},
mounted() {
this.initData()
},
methods: {
formatTrafficIn(row, column) {
return Humanize.fileSize(row.traffic_in)
},
formatTrafficOut(row, column) {
return Humanize.fileSize(row.traffic_out)
},
async initData() {
if (!this.serverInfo) return
this.vhost_http_port = this.serverInfo.vhost_http_port
this.subdomain_host = this.serverInfo.subdomain_host
if (this.vhost_http_port == null || this.vhost_http_port === 0) return
const json = await this.$fetch('proxy/http')
if (!json) return
this.proxies = []
for (const proxyStats of json.proxies) {
this.proxies.push(new HttpProxy(proxyStats, this.vhost_http_port, this.subdomain_host))
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
formatTrafficIn(row, column) {
return Humanize.fileSize(row.traffic_in)
},
formatTrafficOut(row, column) {
return Humanize.fileSize(row.traffic_out)
},
fetchData() {
fetch('/api/serverinfo', {credentials: 'include'})
.then(res => {
return res.json()
}).then(json => {
this.vhost_http_port = json.vhost_http_port
this.subdomain_host = json.subdomain_host
if (this.vhost_http_port == null || this.vhost_http_port == 0) {
return
} else {
fetch('/api/proxy/http', {credentials: 'include'})
.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))
}
})
}
})
}
},
components: {
'my-traffic-chart': Traffic
}
}
}
</script>
<style>
</style>

View File

@ -1,19 +1,14 @@
<template>
<div>
<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">
<template slot-scope="props">
<el-popover
ref="popover4"
placement="right"
width="600"
style="margin-left:0px"
trigger="click">
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
<el-popover ref="popover4" placement="right" width="600" style="margin-left: 0px" trigger="click">
<my-traffic-chart :proxy-name="props.row.name" />
</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>
<el-form label-position="left" inline class="demo-table-expand">
<el-form-item label="Name">
<span>{{ props.row.name }}</span>
@ -39,105 +34,69 @@
<el-form-item label="Last Close">
<span>{{ props.row.last_close_time }}</span>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column
label="Name"
prop="name"
sortable>
</el-table-column>
<el-table-column
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">
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</el-form>
</template>
</el-table-column>
<el-table-column label="Name" prop="name" sortable />
<el-table-column label="Port" prop="port" sortable />
<el-table-column label="Connections" prop="conns" sortable />
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
<el-table-column label="status" prop="status" sortable>
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import Humanize from 'humanize-plus';
import Traffic from './Traffic.vue'
import {
HttpsProxy
} from '../utils/proxy.js'
export default {
data() {
return {
proxies: null,
vhost_https_port: '',
subdomain_host: ''
import Humanize from 'humanize-plus'
import Traffic from './Traffic.vue'
import { HttpsProxy } from '../utils/proxy.js'
export default {
components: {
'my-traffic-chart': Traffic
},
data() {
return {
proxies: [],
vhost_https_port: '',
subdomain_host: ''
}
},
computed: {
serverInfo() {
return this.$store.state.serverInfo
}
},
mounted() {
this.initData()
},
methods: {
formatTrafficIn(row, column) {
return Humanize.fileSize(row.traffic_in)
},
formatTrafficOut(row, column) {
return Humanize.fileSize(row.traffic_out)
},
async initData() {
if (!this.serverInfo) return
this.vhost_https_port = this.serverInfo.vhost_https_port
this.subdomain_host = this.serverInfo.subdomain_host
if (this.vhost_https_port == null || this.vhost_https_port === 0) return
const json = await this.$fetch('proxy/https')
if (!json) return
this.proxies = []
for (const proxyStats of json.proxies) {
this.proxies.push(new HttpsProxy(proxyStats, this.vhost_https_port, this.subdomain_host))
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
formatTrafficIn(row, column) {
return Humanize.fileSize(row.traffic_in)
},
formatTrafficOut(row, column) {
return Humanize.fileSize(row.traffic_out)
},
fetchData() {
fetch('/api/serverinfo', {credentials: 'include'})
.then(res => {
return res.json()
}).then(json => {
this.vhost_https_port = json.vhost_https_port
this.subdomain_host = json.subdomain_host
if (this.vhost_https_port == null || this.vhost_https_port == 0) {
return
} else {
fetch('/api/proxy/https', {credentials: 'include'})
.then(res => {
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))
}
})
}
})
}
},
components: {
'my-traffic-chart': Traffic
}
}
}
</script>
<style>
</style>

View File

@ -1,19 +1,16 @@
<template>
<div>
<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">
<template slot-scope="props">
<el-popover
ref="popover4"
placement="right"
width="600"
style="margin-left:0px"
trigger="click">
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
<el-popover ref="popover4" placement="right" width="600" style="margin-left: 0px" trigger="click">
<my-traffic-chart :proxy-name="props.row.name" />
</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-item label="Name">
<span>{{ props.row.name }}</span>
@ -33,83 +30,57 @@
<el-form-item label="Last Close">
<span>{{ props.row.last_close_time }}</span>
</el-form-item>
</el-form>
</el-form>
</template>
</el-table-column>
<el-table-column
label="Name"
prop="name"
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">
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</el-table-column>
<el-table-column label="Name" prop="name" sortable />
<el-table-column label="Connections" prop="conns" sortable />
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
<el-table-column label="status" prop="status" sortable>
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import Humanize from 'humanize-plus'
import Traffic from './Traffic.vue'
import { StcpProxy } from '../utils/proxy.js'
export default {
data() {
return {
proxies: null
import Humanize from 'humanize-plus'
import Traffic from './Traffic.vue'
import { StcpProxy } from '../utils/proxy.js'
export default {
components: {
'my-traffic-chart': Traffic
},
data() {
return {
proxies: []
}
},
mounted() {
this.initData()
},
methods: {
formatTrafficIn(row, column) {
return Humanize.fileSize(row.traffic_in)
},
formatTrafficOut(row, column) {
return Humanize.fileSize(row.traffic_out)
},
async initData() {
const json = await this.$fetch('proxy/stcp')
if (!json) return
this.proxies = []
for (const proxyStats of json.proxies) {
this.proxies.push(new StcpProxy(proxyStats))
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
formatTrafficIn(row, column) {
return Humanize.fileSize(row.traffic_in)
},
formatTrafficOut(row, column) {
return Humanize.fileSize(row.traffic_out)
},
fetchData() {
fetch('/api/proxy/stcp', {credentials: 'include'})
.then(res => {
return res.json()
}).then(json => {
this.proxies = new Array()
for (let proxyStats of json.proxies) {
this.proxies.push(new StcpProxy(proxyStats))
}
})
}
},
components: {
'my-traffic-chart': Traffic
}
}
}
</script>
<style>

View File

@ -1,19 +1,16 @@
<template>
<div>
<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">
<template slot-scope="props">
<el-popover
ref="popover4"
placement="right"
width="600"
style="margin-left:0px"
trigger="click">
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
<el-popover placement="right" width="600" style="margin-left: 0px" trigger="click">
<my-traffic-chart :proxy-name="props.row.name" />
<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-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-item label="Name">
<span>{{ props.row.name }}</span>
@ -36,88 +33,58 @@
<el-form-item label="Last Close">
<span>{{ props.row.last_close_time }}</span>
</el-form-item>
</el-form>
</el-form>
</template>
</el-table-column>
<el-table-column
label="Name"
prop="name"
sortable>
</el-table-column>
<el-table-column
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">
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</el-table-column>
<el-table-column label="Name" prop="name" sortable />
<el-table-column label="Port" prop="port" sortable />
<el-table-column label="Connections" prop="conns" sortable />
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
<el-table-column label="status" prop="status" sortable>
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import Humanize from 'humanize-plus'
import Traffic from './Traffic.vue'
import { TcpProxy } from '../utils/proxy.js'
export default {
data() {
return {
proxies: null
import Humanize from 'humanize-plus'
import Traffic from './Traffic.vue'
import { TcpProxy } from '../utils/proxy.js'
export default {
components: {
'my-traffic-chart': Traffic
},
data() {
return {
proxies: []
}
},
mounted() {
this.initData()
},
methods: {
formatTrafficIn(row, column) {
return Humanize.fileSize(row.traffic_in)
},
formatTrafficOut(row, column) {
return Humanize.fileSize(row.traffic_out)
},
async initData() {
const json = await this.$fetch('proxy/tcp')
if (!json) return
this.proxies = []
for (const proxyStats of json.proxies) {
this.proxies.push(new TcpProxy(proxyStats))
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
formatTrafficIn(row, column) {
return Humanize.fileSize(row.traffic_in)
},
formatTrafficOut(row, column) {
return Humanize.fileSize(row.traffic_out)
},
fetchData() {
fetch('/api/proxy/tcp', {credentials: 'include'})
.then(res => {
return res.json()
}).then(json => {
this.proxies = new Array()
for (let proxyStats of json.proxies) {
this.proxies.push(new TcpProxy(proxyStats))
}
})
}
},
components: {
'my-traffic-chart': Traffic
}
}
}
</script>
<style>

View File

@ -1,19 +1,14 @@
<template>
<div>
<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">
<template slot-scope="props">
<el-popover
ref="popover4"
placement="right"
width="600"
style="margin-left:0px"
trigger="click">
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart>
<el-popover ref="popover4" placement="right" width="600" style="margin-left: 0px" trigger="click">
<my-traffic-chart :proxy-name="props.row.name" />
</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>
<el-form label-position="left" inline class="demo-table-expand">
<el-form-item label="Name">
<span>{{ props.row.name }}</span>
@ -36,91 +31,57 @@
<el-form-item label="Last Close">
<span>{{ props.row.last_close_time }}</span>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column
label="Name"
prop="name"
sortable>
</el-table-column>
<el-table-column
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">
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag>
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</el-form>
</template>
</el-table-column>
<el-table-column label="Name" prop="name" sortable />
<el-table-column label="Port" prop="port" sortable />
<el-table-column label="Connections" prop="conns" sortable />
<el-table-column label="Traffic In" prop="traffic_in" :formatter="formatTrafficIn" sortable />
<el-table-column label="Traffic Out" prop="traffic_out" :formatter="formatTrafficOut" sortable />
<el-table-column label="status" prop="status" sortable>
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 'online'" type="success">{{ scope.row.status }}</el-tag>
<el-tag v-else type="danger">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import Humanize from 'humanize-plus';
import Traffic from './Traffic.vue'
import {
UdpProxy
} from '../utils/proxy.js'
export default {
data() {
return {
proxies: null
import Humanize from 'humanize-plus'
import Traffic from './Traffic.vue'
import { UdpProxy } from '../utils/proxy.js'
export default {
components: {
'my-traffic-chart': Traffic
},
data() {
return {
proxies: []
}
},
mounted() {
this.initData()
},
methods: {
formatTrafficIn(row, column) {
return Humanize.fileSize(row.traffic_in)
},
formatTrafficOut(row, column) {
return Humanize.fileSize(row.traffic_out)
},
async initData() {
const json = await this.$fetch('proxy/udp')
if (!json) return
this.proxies = []
for (const proxyStats of json.proxies) {
this.proxies.push(new UdpProxy(proxyStats))
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
formatTrafficIn(row, column) {
return Humanize.fileSize(row.traffic_in)
},
formatTrafficOut(row, column) {
return Humanize.fileSize(row.traffic_out)
},
fetchData() {
fetch('/api/proxy/udp', {credentials: 'include'})
.then(res => {
return res.json()
}).then(json => {
this.proxies = new Array()
for (let proxyStats of json.proxies) {
this.proxies.push(new UdpProxy(proxyStats))
}
})
}
},
components: {
'my-traffic-chart': Traffic
}
}
}
</script>
<style>
</style>

View File

@ -1,36 +1,26 @@
<template>
<div :id="proxy_name" style="width: 600px;height:400px;"></div>
<div :id="proxyName" style="width: 600px; height: 400px" />
</template>
<script>
import {DrawProxyTrafficChart} from '../utils/chart.js'
import { DrawProxyTrafficChart } from '../utils/chart.js'
export default {
props: ['proxy_name'],
created() {
this.fetchData()
},
//watch: {
//'$route': 'fetchData'
//},
methods: {
fetchData() {
let url = '/api/traffic/' + this.proxy_name
fetch(url, {credentials: 'include'})
.then(res => {
return res.json()
}).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'
})
})
}
props: {
proxyName: {
type: String,
required: true
}
},
mounted() {
this.initData()
},
methods: {
async initData() {
const json = await this.$fetch(`traffic/${this.proxyName}`)
if (!json) return
DrawProxyTrafficChart(this.proxyName, json.traffic_in, json.traffic_out)
}
}
}
</script>
<style>
</style>

View File

@ -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>

View File

@ -1,19 +1,6 @@
import Vue from 'vue'
//import ElementUI from 'element-ui'
import {
Button,
Form,
FormItem,
Row,
Col,
Table,
TableColumn,
Popover,
Menu,
Submenu,
MenuItem,
Tag
} from 'element-ui'
// import ElementUI from 'element-ui'
import { Button, Form, FormItem, Row, Col, Table, TableColumn, Popover, Menu, Submenu, MenuItem, Tag, Message } from 'element-ui'
import lang from 'element-ui/lib/locale/lang/en'
import locale from 'element-ui/lib/locale'
import 'element-ui/lib/theme-chalk/index.css'
@ -21,6 +8,7 @@ import './utils/less/custom.less'
import App from './App.vue'
import router from './router'
import store from '@/store'
import 'whatwg-fetch'
locale.use(lang)
@ -37,12 +25,15 @@ Vue.use(Menu)
Vue.use(Submenu)
Vue.use(MenuItem)
Vue.use(Tag)
Vue.prototype.$message = Message
import fetch from '@/utils/fetch'
Vue.prototype.$fetch = fetch
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
router,
store,
render: h => h(App)
}).$mount('#app')

View File

@ -10,29 +10,36 @@ import ProxiesStcp from '../components/ProxiesStcp.vue'
Vue.use(Router)
export default new Router({
routes: [{
path: '/',
name: 'Overview',
component: Overview
}, {
path: '/proxies/tcp',
name: 'ProxiesTcp',
component: ProxiesTcp
}, {
path: '/proxies/udp',
name: 'ProxiesUdp',
component: ProxiesUdp
}, {
path: '/proxies/http',
name: 'ProxiesHttp',
component: ProxiesHttp
}, {
path: '/proxies/https',
name: 'ProxiesHttps',
component: ProxiesHttps
}, {
path: '/proxies/stcp',
name: 'ProxiesStcp',
component: ProxiesStcp
}]
routes: [
{
path: '/',
name: 'Overview',
component: Overview
},
{
path: '/proxies/tcp',
name: 'ProxiesTcp',
component: ProxiesTcp
},
{
path: '/proxies/udp',
name: 'ProxiesUdp',
component: ProxiesUdp
},
{
path: '/proxies/http',
name: 'ProxiesHttp',
component: ProxiesHttp
},
{
path: '/proxies/https',
name: 'ProxiesHttps',
component: ProxiesHttps
},
{
path: '/proxies/stcp',
name: 'ProxiesStcp',
component: ProxiesStcp
}
]
})

View 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

View File

@ -1,199 +1,215 @@
import Humanize from "humanize-plus"
import echarts from "echarts/lib/echarts"
import Humanize from 'humanize-plus'
import echarts from 'echarts/lib/echarts'
import "echarts/theme/macarons"
import "echarts/lib/chart/bar"
import "echarts/lib/chart/pie"
import "echarts/lib/component/tooltip"
import "echarts/lib/component/title"
import 'echarts/theme/macarons'
import 'echarts/lib/chart/bar'
import 'echarts/lib/chart/pie'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/title'
function DrawTrafficChart(elementId, trafficIn, trafficOut) {
let myChart = echarts.init(document.getElementById(elementId), 'macarons');
myChart.showLoading()
const myChart = echarts.init(document.getElementById(elementId), 'macarons')
myChart.showLoading()
let option = {
title: {
text: 'Network Traffic',
subtext: 'today',
x: 'center'
},
tooltip: {
trigger: 'item',
formatter: function(v) {
return Humanize.fileSize(v.data.value) + " (" + v.percent + "%)"
}
},
series: [{
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: [{
value: trafficIn,
name: 'Traffic In'
}, {
value: trafficOut,
name: 'Traffic Out'
}, ],
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
};
myChart.setOption(option);
myChart.hideLoading()
const option = {
title: {
text: 'Network Traffic',
subtext: 'today',
x: 'center'
},
tooltip: {
trigger: 'item',
formatter: function(v) {
return Humanize.fileSize(v.data.value) + ' (' + v.percent + '%)'
}
},
series: [
{
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: [
{
value: trafficIn,
name: 'Traffic In'
},
{
value: trafficOut,
name: 'Traffic Out'
}
],
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
myChart.setOption(option)
myChart.hideLoading()
}
function DrawProxyChart(elementId, serverInfo) {
if (serverInfo.proxy_type_count.tcp == null) {
serverInfo.proxy_type_count.tcp = 0
}
if (serverInfo.proxy_type_count.udp == null) {
serverInfo.proxy_type_count.udp = 0
}
if (serverInfo.proxy_type_count.http == null) {
serverInfo.proxy_type_count.http = 0
}
if (serverInfo.proxy_type_count.https == null) {
serverInfo.proxy_type_count.https = 0
}
if (serverInfo.proxy_type_count.stcp == null) {
serverInfo.proxy_type_count.stcp = 0
}
if (serverInfo.proxy_type_count.xtcp == null) {
serverInfo.proxy_type_count.xtcp = 0
}
let myChart = echarts.init(document.getElementById(elementId), 'macarons')
myChart.showLoading()
if (serverInfo.proxy_type_count.tcp == null) {
serverInfo.proxy_type_count.tcp = 0
}
if (serverInfo.proxy_type_count.udp == null) {
serverInfo.proxy_type_count.udp = 0
}
if (serverInfo.proxy_type_count.http == null) {
serverInfo.proxy_type_count.http = 0
}
if (serverInfo.proxy_type_count.https == null) {
serverInfo.proxy_type_count.https = 0
}
if (serverInfo.proxy_type_count.stcp == null) {
serverInfo.proxy_type_count.stcp = 0
}
if (serverInfo.proxy_type_count.xtcp == null) {
serverInfo.proxy_type_count.xtcp = 0
}
const myChart = echarts.init(document.getElementById(elementId), 'macarons')
myChart.showLoading()
let option = {
title: {
text: 'Proxies',
subtext: 'now',
x: 'center'
},
tooltip: {
trigger: 'item',
formatter: function(v) {
return v.data.value
}
},
series: [{
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: [{
value: serverInfo.proxy_type_count.tcp,
name: 'TCP'
}, {
value: serverInfo.proxy_type_count.udp,
name: 'UDP'
}, {
value: serverInfo.proxy_type_count.http,
name: 'HTTP'
}, {
value: serverInfo.proxy_type_count.https,
name: 'HTTPS'
}, {
value: serverInfo.proxy_type_count.stcp,
name: 'STCP'
}, {
value: serverInfo.proxy_type_count.xtcp,
name: 'XTCP'
}],
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
};
myChart.setOption(option);
myChart.hideLoading()
const option = {
title: {
text: 'Proxies',
subtext: 'now',
x: 'center'
},
tooltip: {
trigger: 'item',
formatter: function(v) {
return v.data.value
}
},
series: [
{
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: [
{
value: serverInfo.proxy_type_count.tcp,
name: 'TCP'
},
{
value: serverInfo.proxy_type_count.udp,
name: 'UDP'
},
{
value: serverInfo.proxy_type_count.http,
name: 'HTTP'
},
{
value: serverInfo.proxy_type_count.https,
name: 'HTTPS'
},
{
value: serverInfo.proxy_type_count.stcp,
name: 'STCP'
},
{
value: serverInfo.proxy_type_count.xtcp,
name: 'XTCP'
}
],
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
myChart.setOption(option)
myChart.hideLoading()
}
// 7 days
function DrawProxyTrafficChart(elementId, trafficInArr, trafficOutArr) {
let params = {
width: '600px',
height: '400px'
}
const params = {
width: '600px',
height: '400px'
}
let myChart = echarts.init(document.getElementById(elementId), 'macarons', params);
myChart.showLoading()
const myChart = echarts.init(document.getElementById(elementId), 'macarons', params)
myChart.showLoading()
trafficInArr = trafficInArr.reverse()
trafficOutArr = trafficOutArr.reverse()
let now = new Date()
now = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6)
let dates = new Array()
for (let i = 0; i < 7; i++) {
dates.push(now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate())
now = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1)
}
trafficInArr = trafficInArr.reverse()
trafficOutArr = trafficOutArr.reverse()
let now = new Date()
now = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6)
const dates = []
for (let i = 0; i < 7; i++) {
dates.push(now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate())
now = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1)
}
let option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function(data) {
let html = ''
if (data.length > 0) {
html += data[0].name + '<br/>'
}
for (let v of data) {
let colorEl = '<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/>'
}
return html
}
},
legend: {
data: ['Traffic In', 'Traffic Out']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [{
type: 'category',
data: dates
}],
yAxis: [{
type: 'value',
axisLabel: {
formatter: function(value) {
return Humanize.fileSize(value)
}
}
}],
series: [{
name: 'Traffic In',
type: 'bar',
data: trafficInArr
}, {
name: 'Traffic Out',
type: 'bar',
data: trafficOutArr
}]
};
myChart.setOption(option);
myChart.hideLoading()
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function(data) {
let html = ''
if (data.length > 0) {
html += data[0].name + '<br/>'
}
for (const v of data) {
const colorEl =
'<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/>'
}
return html
}
},
legend: {
data: ['Traffic In', 'Traffic Out']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: dates
}
],
yAxis: [
{
type: 'value',
axisLabel: {
formatter: function(value) {
return Humanize.fileSize(value)
}
}
}
],
series: [
{
name: 'Traffic In',
type: 'bar',
data: trafficInArr
},
{
name: 'Traffic Out',
type: 'bar',
data: trafficOutArr
}
]
}
myChart.setOption(option)
myChart.hideLoading()
}
export {
DrawTrafficChart,
DrawProxyChart,
DrawProxyTrafficChart
}
export { DrawTrafficChart, DrawProxyChart, DrawProxyTrafficChart }

View 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()
})
})
}

View File

@ -1,97 +1,97 @@
class BaseProxy {
constructor(proxyStats) {
this.name = proxyStats.name
if (proxyStats.conf != null) {
this.encryption = proxyStats.conf.use_encryption
this.compression = proxyStats.conf.use_compression
} else {
this.encryption = ""
this.compression = ""
}
this.conns = proxyStats.cur_conns
this.traffic_in = proxyStats.today_traffic_in
this.traffic_out = proxyStats.today_traffic_out
this.last_start_time = proxyStats.last_start_time
this.last_close_time = proxyStats.last_close_time
this.status = proxyStats.status
constructor(proxyStats) {
this.name = proxyStats.name
if (proxyStats.conf != null) {
this.encryption = proxyStats.conf.use_encryption
this.compression = proxyStats.conf.use_compression
} else {
this.encryption = ''
this.compression = ''
}
this.conns = proxyStats.cur_conns
this.traffic_in = proxyStats.today_traffic_in
this.traffic_out = proxyStats.today_traffic_out
this.last_start_time = proxyStats.last_start_time
this.last_close_time = proxyStats.last_close_time
this.status = proxyStats.status
}
}
class TcpProxy extends BaseProxy {
constructor(proxyStats) {
super(proxyStats)
this.type = "tcp"
if (proxyStats.conf != null) {
this.addr = ":" + proxyStats.conf.remote_port
this.port = proxyStats.conf.remote_port
} else {
this.addr = ""
this.port = ""
}
constructor(proxyStats) {
super(proxyStats)
this.type = 'tcp'
if (proxyStats.conf != null) {
this.addr = ':' + proxyStats.conf.remote_port
this.port = proxyStats.conf.remote_port
} else {
this.addr = ''
this.port = ''
}
}
}
class UdpProxy extends BaseProxy {
constructor(proxyStats) {
super(proxyStats)
this.type = "udp"
if (proxyStats.conf != null) {
this.addr = ":" + proxyStats.conf.remote_port
this.port = proxyStats.conf.remote_port
} else {
this.addr = ""
this.port = ""
}
constructor(proxyStats) {
super(proxyStats)
this.type = 'udp'
if (proxyStats.conf != null) {
this.addr = ':' + proxyStats.conf.remote_port
this.port = proxyStats.conf.remote_port
} else {
this.addr = ''
this.port = ''
}
}
}
class HttpProxy extends BaseProxy {
constructor(proxyStats, port, subdomain_host) {
super(proxyStats)
this.type = "http"
this.port = port
if (proxyStats.conf != null) {
this.custom_domains = proxyStats.conf.custom_domains
this.host_header_rewrite = proxyStats.conf.host_header_rewrite
this.locations = proxyStats.conf.locations
if (proxyStats.conf.sub_domain != "") {
this.subdomain = proxyStats.conf.sub_domain + "." + subdomain_host
} else {
this.subdomain = ""
}
} else {
this.custom_domains = ""
this.host_header_rewrite = ""
this.subdomain = ""
this.locations = ""
}
constructor(proxyStats, port, subdomain_host) {
super(proxyStats)
this.type = 'http'
this.port = port
if (proxyStats.conf != null) {
this.custom_domains = proxyStats.conf.custom_domains
this.host_header_rewrite = proxyStats.conf.host_header_rewrite
this.locations = proxyStats.conf.locations
if (proxyStats.conf.sub_domain !== '') {
this.subdomain = proxyStats.conf.sub_domain + '.' + subdomain_host
} else {
this.subdomain = ''
}
} else {
this.custom_domains = ''
this.host_header_rewrite = ''
this.subdomain = ''
this.locations = ''
}
}
}
class HttpsProxy extends BaseProxy {
constructor(proxyStats, port, subdomain_host) {
super(proxyStats)
this.type = "https"
this.port = port
if (proxyStats.conf != null) {
this.custom_domains = proxyStats.conf.custom_domains
if (proxyStats.conf.sub_domain != "") {
this.subdomain = proxyStats.conf.sub_domain + "." + subdomain_host
} else {
this.subdomain = ""
}
} else {
this.custom_domains = ""
this.subdomain = ""
}
constructor(proxyStats, port, subdomain_host) {
super(proxyStats)
this.type = 'https'
this.port = port
if (proxyStats.conf != null) {
this.custom_domains = proxyStats.conf.custom_domains
if (proxyStats.conf.sub_domain !== '') {
this.subdomain = proxyStats.conf.sub_domain + '.' + subdomain_host
} else {
this.subdomain = ''
}
} else {
this.custom_domains = ''
this.subdomain = ''
}
}
}
class StcpProxy extends BaseProxy {
constructor(proxyStats) {
super(proxyStats)
this.type = "stcp"
}
constructor(proxyStats) {
super(proxyStats)
this.type = 'stcp'
}
}
export {BaseProxy, TcpProxy, UdpProxy, HttpProxy, HttpsProxy, StcpProxy}
export { BaseProxy, TcpProxy, UdpProxy, HttpProxy, HttpsProxy, StcpProxy }

16
web/frps/vue.config.js Normal file
View 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': ''
}
}
}
}
}

View File

@ -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

File diff suppressed because it is too large Load Diff