1
1
mirror of https://github.com/fatedier/frp.git synced 2025-07-05 15:00:06 +02:00

Merge pull request from fatedier/dev

bump version to v0.24.0
This commit is contained in:
fatedier
2019-02-11 14:46:23 +08:00
committed by GitHub
56 changed files with 16410 additions and 152 deletions

@ -6,11 +6,12 @@ build: frps frpc
# compile assets into binary file # compile assets into binary file
file: file:
rm -rf ./assets/static/* rm -rf ./assets/frps/static/*
cp -rf ./web/frps/dist/* ./assets/static rm -rf ./assets/frpc/static/*
go get -d github.com/rakyll/statik cp -rf ./web/frps/dist/* ./assets/frps/static
go install github.com/rakyll/statik cp -rf ./web/frpc/dist/* ./assets/frpc/static
rm -rf ./assets/statik rm -rf ./assets/frps/statik
rm -rf ./assets/frpc/statik
go generate ./assets/... go generate ./assets/...
fmt: fmt:
@ -18,7 +19,6 @@ fmt:
frps: frps:
go build -o bin/frps ./cmd/frps go build -o bin/frps ./cmd/frps
@cp -rf ./assets/static ./bin
frpc: frpc:
go build -o bin/frpc ./cmd/frpc go build -o bin/frpc ./cmd/frpc

@ -14,8 +14,10 @@
package assets package assets
//go:generate statik -src=./static //go:generate statik -src=./frps/static -dest=./frps
//go:generate go fmt statik/statik.go //go:generate statik -src=./frpc/static -dest=./frpc
//go:generate go fmt ./frps/statik/statik.go
//go:generate go fmt ./frpc/statik/statik.go
import ( import (
"io/ioutil" "io/ioutil"
@ -24,8 +26,6 @@ import (
"path" "path"
"github.com/rakyll/statik/fs" "github.com/rakyll/statik/fs"
_ "github.com/fatedier/frp/assets/statik"
) )
var ( var (

Before

Width: 48px  |  Height: 48px  |  Size: 9.4 KiB

After

Width: 48px  |  Height: 48px  |  Size: 9.4 KiB

@ -0,0 +1 @@
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?d2cd6337d30c7b22e836"></script><script type="text/javascript" src="vendor.js?edb271e1d9c81f857840"></script></body> </html>

@ -0,0 +1 @@
!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:"edb271e1d9c81f857840"}[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

Binary file not shown.

Binary file not shown.

After

Width: 48px  |  Height: 48px  |  Size: 9.4 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -20,6 +20,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
frpNet "github.com/fatedier/frp/utils/net" frpNet "github.com/fatedier/frp/utils/net"
@ -41,6 +42,15 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
// api, see dashboard_api.go // api, see dashboard_api.go
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET") router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET") router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
router.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
// view
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
})
address := fmt.Sprintf("%s:%d", addr, port) address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{ server := &http.Server{

@ -17,6 +17,7 @@ package client
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"sort" "sort"
"strings" "strings"
@ -28,57 +29,53 @@ import (
) )
type GeneralResponse struct { type GeneralResponse struct {
Code int64 `json:"code"` Code int
Msg string `json:"msg"` Msg string
} }
// api/reload // GET api/reload
type ReloadResp struct {
GeneralResponse
}
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) { func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res ReloadResp
)
defer func() {
log.Info("Http response [/api/reload]: code [%d]", res.Code)
buf, _ = json.Marshal(&res)
w.Write(buf)
}()
log.Info("Http request: [/api/reload]") log.Info("Http request [/api/reload]")
defer func() {
log.Info("Http response [/api/reload], code [%d]", res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}()
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile) content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
if err != nil { if err != nil {
res.Code = 1 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc config file error: %v", err) log.Warn("reload frpc config file error: %s", res.Msg)
return return
} }
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content) newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
if err != nil { if err != nil {
res.Code = 2 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc common section error: %v", err) log.Warn("reload frpc common section error: %s", res.Msg)
return return
} }
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start) pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
if err != nil { if err != nil {
res.Code = 3 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc proxy config error: %v", err) log.Warn("reload frpc proxy config error: %s", res.Msg)
return return
} }
err = svr.ReloadConf(pxyCfgs, visitorCfgs) err = svr.ReloadConf(pxyCfgs, visitorCfgs)
if err != nil { if err != nil {
res.Code = 4 res.Code = 500
res.Msg = err.Error() res.Msg = err.Error()
log.Error("reload frpc proxy config error: %v", err) log.Warn("reload frpc proxy config error: %s", res.Msg)
return return
} }
log.Info("success reload conf") log.Info("success reload conf")
@ -163,7 +160,7 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
return psr return psr
} }
// api/status // GET api/status
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) { func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
var ( var (
buf []byte buf []byte
@ -175,14 +172,14 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
res.Https = make([]ProxyStatusResp, 0) res.Https = make([]ProxyStatusResp, 0)
res.Stcp = make([]ProxyStatusResp, 0) res.Stcp = make([]ProxyStatusResp, 0)
res.Xtcp = make([]ProxyStatusResp, 0) res.Xtcp = make([]ProxyStatusResp, 0)
log.Info("Http request [/api/status]")
defer func() { defer func() {
log.Info("Http response [/api/status]") log.Info("Http response [/api/status]")
buf, _ = json.Marshal(&res) buf, _ = json.Marshal(&res)
w.Write(buf) w.Write(buf)
}() }()
log.Info("Http request: [/api/status]")
ps := svr.ctl.pm.GetAllProxyStatus() ps := svr.ctl.pm.GetAllProxyStatus()
for _, status := range ps { for _, status := range ps {
switch status.Type { switch status.Type {
@ -208,3 +205,120 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
sort.Sort(ByProxyStatusResp(res.Xtcp)) sort.Sort(ByProxyStatusResp(res.Xtcp))
return return
} }
// GET api/config
func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
log.Info("Http get request [/api/config]")
defer func() {
log.Info("Http get response [/api/config], code [%d]", res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}()
if g.GlbClientCfg.CfgFile == "" {
res.Code = 400
res.Msg = "frpc has no config file path"
log.Warn("%s", res.Msg)
return
}
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
if err != nil {
res.Code = 400
res.Msg = err.Error()
log.Warn("load frpc config file error: %s", res.Msg)
return
}
rows := strings.Split(content, "\n")
newRows := make([]string, 0, len(rows))
for _, row := range rows {
row = strings.TrimSpace(row)
if strings.HasPrefix(row, "token") {
continue
}
newRows = append(newRows, row)
}
res.Msg = strings.Join(newRows, "\n")
}
// PUT api/config
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
log.Info("Http put request [/api/config]")
defer func() {
log.Info("Http put response [/api/config], code [%d]", res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}()
// get new config content
body, err := ioutil.ReadAll(r.Body)
if err != nil {
res.Code = 400
res.Msg = fmt.Sprintf("read request body error: %v", err)
log.Warn("%s", res.Msg)
return
}
if len(body) == 0 {
res.Code = 400
res.Msg = "body can't be empty"
log.Warn("%s", res.Msg)
return
}
// get token from origin content
token := ""
b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
if err != nil {
res.Code = 400
res.Msg = err.Error()
log.Warn("load frpc config file error: %s", res.Msg)
return
}
content := string(b)
for _, row := range strings.Split(content, "\n") {
row = strings.TrimSpace(row)
if strings.HasPrefix(row, "token") {
token = row
break
}
}
tmpRows := make([]string, 0)
for _, row := range strings.Split(string(body), "\n") {
row = strings.TrimSpace(row)
if strings.HasPrefix(row, "token") {
continue
}
tmpRows = append(tmpRows, row)
}
newRows := make([]string, 0)
if token != "" {
for _, row := range tmpRows {
newRows = append(newRows, row)
if strings.HasPrefix(row, "[common]") {
newRows = append(newRows, token)
}
}
}
content = strings.Join(newRows, "\n")
err = ioutil.WriteFile(g.GlbClientCfg.CfgFile, []byte(content), 0644)
if err != nil {
res.Code = 500
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
log.Warn("%s", res.Msg)
return
}
}

@ -53,32 +53,32 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
switch cfg := pxyConf.(type) { switch cfg := pxyConf.(type) {
case *config.TcpProxyConf: case *config.TcpProxyConf:
pxy = &TcpProxy{ pxy = &TcpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.UdpProxyConf: case *config.UdpProxyConf:
pxy = &UdpProxy{ pxy = &UdpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.HttpProxyConf: case *config.HttpProxyConf:
pxy = &HttpProxy{ pxy = &HttpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.HttpsProxyConf: case *config.HttpsProxyConf:
pxy = &HttpsProxy{ pxy = &HttpsProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.StcpProxyConf: case *config.StcpProxyConf:
pxy = &StcpProxy{ pxy = &StcpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
case *config.XtcpProxyConf: case *config.XtcpProxyConf:
pxy = &XtcpProxy{ pxy = &XtcpProxy{
BaseProxy: baseProxy, BaseProxy: &baseProxy,
cfg: cfg, cfg: cfg,
} }
} }
@ -93,7 +93,7 @@ type BaseProxy struct {
// TCP // TCP
type TcpProxy struct { type TcpProxy struct {
BaseProxy *BaseProxy
cfg *config.TcpProxyConf cfg *config.TcpProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@ -122,7 +122,7 @@ func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
// HTTP // HTTP
type HttpProxy struct { type HttpProxy struct {
BaseProxy *BaseProxy
cfg *config.HttpProxyConf cfg *config.HttpProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@ -151,7 +151,7 @@ func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
// HTTPS // HTTPS
type HttpsProxy struct { type HttpsProxy struct {
BaseProxy *BaseProxy
cfg *config.HttpsProxyConf cfg *config.HttpsProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@ -180,7 +180,7 @@ func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
// STCP // STCP
type StcpProxy struct { type StcpProxy struct {
BaseProxy *BaseProxy
cfg *config.StcpProxyConf cfg *config.StcpProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@ -209,7 +209,7 @@ func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
// XTCP // XTCP
type XtcpProxy struct { type XtcpProxy struct {
BaseProxy *BaseProxy
cfg *config.XtcpProxyConf cfg *config.XtcpProxyConf
proxyPlugin plugin.Plugin proxyPlugin plugin.Plugin
@ -308,7 +308,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
// UDP // UDP
type UdpProxy struct { type UdpProxy struct {
BaseProxy *BaseProxy
cfg *config.UdpProxyConf cfg *config.UdpProxyConf

@ -22,6 +22,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/msg"
@ -49,7 +50,14 @@ type Service struct {
closedCh chan int closedCh chan int
} }
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service) { func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service, err error) {
// Init assets
err = assets.Load("")
if err != nil {
err = fmt.Errorf("Load assets error: %v", err)
return
}
svr = &Service{ svr = &Service{
pxyCfgs: pxyCfgs, pxyCfgs: pxyCfgs,
visitorCfgs: visitorCfgs, visitorCfgs: visitorCfgs,

@ -52,12 +52,12 @@ func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
switch cfg := cfg.(type) { switch cfg := cfg.(type) {
case *config.StcpVisitorConf: case *config.StcpVisitorConf:
visitor = &StcpVisitor{ visitor = &StcpVisitor{
BaseVisitor: baseVisitor, BaseVisitor: &baseVisitor,
cfg: cfg, cfg: cfg,
} }
case *config.XtcpVisitorConf: case *config.XtcpVisitorConf:
visitor = &XtcpVisitor{ visitor = &XtcpVisitor{
BaseVisitor: baseVisitor, BaseVisitor: &baseVisitor,
cfg: cfg, cfg: cfg,
} }
} }
@ -73,7 +73,7 @@ type BaseVisitor struct {
} }
type StcpVisitor struct { type StcpVisitor struct {
BaseVisitor *BaseVisitor
cfg *config.StcpVisitorConf cfg *config.StcpVisitorConf
} }
@ -160,7 +160,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
} }
type XtcpVisitor struct { type XtcpVisitor struct {
BaseVisitor *BaseVisitor
cfg *config.XtcpVisitorConf cfg *config.XtcpVisitorConf
} }

@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package main // "github.com/fatedier/frp/cmd/frpc" package main
import ( import (
_ "github.com/fatedier/frp/assets/frpc/statik"
"github.com/fatedier/frp/cmd/frpc/sub" "github.com/fatedier/frp/cmd/frpc/sub"
"github.com/fatedier/golib/crypto" "github.com/fatedier/golib/crypto"

@ -16,7 +16,6 @@ package sub
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -25,7 +24,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/g" "github.com/fatedier/frp/g"
"github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/config"
) )
@ -79,21 +77,16 @@ func reload() error {
if err != nil { if err != nil {
return err return err
} else { } else {
if resp.StatusCode != 200 { if resp.StatusCode == 200 {
return fmt.Errorf("admin api status code [%d]", resp.StatusCode) return nil
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return err return err
} }
res := &client.GeneralResponse{} return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
err = json.Unmarshal(body, &res)
if err != nil {
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
} else if res.Code != 0 {
return fmt.Errorf(res.Msg)
}
} }
return nil return nil
} }

@ -205,7 +205,11 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
}, },
} }
} }
svr := client.NewService(pxyCfgs, visitorCfgs) svr, errRet := client.NewService(pxyCfgs, visitorCfgs)
if errRet != nil {
err = errRet
return
}
// Capture the exit signal if we use kcp. // Capture the exit signal if we use kcp.
if g.GlbClientCfg.Protocol == "kcp" { if g.GlbClientCfg.Protocol == "kcp" {

@ -12,10 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package main // "github.com/fatedier/frp/cmd/frps" package main
import ( import (
"github.com/fatedier/golib/crypto" "github.com/fatedier/golib/crypto"
_ "github.com/fatedier/frp/assets/frps/statik"
) )
func main() { func main() {

@ -51,7 +51,7 @@ type ServerCommonConf struct {
VhostHttpPort int `json:"vhost_http_port"` VhostHttpPort int `json:"vhost_http_port"`
// if VhostHttpsPort equals 0, don't listen a public port for https protocol // if VhostHttpsPort equals 0, don't listen a public port for https protocol
VhostHttpsPort int `json:"vhost_http_port"` VhostHttpsPort int `json:"vhost_https_port"`
VhostHttpTimeout int64 `json:"vhost_http_timeout"` VhostHttpTimeout int64 `json:"vhost_http_timeout"`

@ -67,7 +67,6 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
default: default:
} }
} }
return
} }
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) { func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {

@ -28,13 +28,11 @@ import (
) )
type GeneralResponse struct { type GeneralResponse struct {
Code int64 `json:"code"` Code int
Msg string `json:"msg"` Msg string
} }
type ServerInfoResp struct { type ServerInfoResp struct {
GeneralResponse
Version string `json:"version"` Version string `json:"version"`
BindPort int `json:"bind_port"` BindPort int `json:"bind_port"`
BindUdpPort int `json:"bind_udp_port"` BindUdpPort int `json:"bind_udp_port"`
@ -55,18 +53,19 @@ type ServerInfoResp struct {
// api/serverinfo // api/serverinfo
func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) { func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res ServerInfoResp
)
defer func() { defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}() }()
log.Info("Http request: [%s]", r.URL.Path) log.Info("Http request: [%s]", r.URL.Path)
cfg := &g.GlbServerCfg.ServerCommonConf cfg := &g.GlbServerCfg.ServerCommonConf
serverStats := svr.statsCollector.GetServer() serverStats := svr.statsCollector.GetServer()
res = ServerInfoResp{ svrResp := ServerInfoResp{
Version: version.Full(), Version: version.Full(),
BindPort: cfg.BindPort, BindPort: cfg.BindPort,
BindUdpPort: cfg.BindUdpPort, BindUdpPort: cfg.BindUdpPort,
@ -85,8 +84,8 @@ func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
ProxyTypeCounts: serverStats.ProxyTypeCounts, ProxyTypeCounts: serverStats.ProxyTypeCounts,
} }
buf, _ = json.Marshal(&res) buf, _ := json.Marshal(&svrResp)
w.Write(buf) res.Msg = string(buf)
} }
type BaseOutConf struct { type BaseOutConf struct {
@ -155,31 +154,29 @@ type ProxyStatsInfo struct {
} }
type GetProxyInfoResp struct { type GetProxyInfoResp struct {
GeneralResponse
Proxies []*ProxyStatsInfo `json:"proxies"` Proxies []*ProxyStatsInfo `json:"proxies"`
} }
// api/proxy/:type // api/proxy/:type
func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) { func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res GetProxyInfoResp
)
params := mux.Vars(r) params := mux.Vars(r)
proxyType := params["type"] proxyType := params["type"]
defer func() { defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
log.Info(r.URL.Path) w.WriteHeader(res.Code)
log.Info(r.URL.RawPath) if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}() }()
log.Info("Http request: [%s]", r.URL.Path) log.Info("Http request: [%s]", r.URL.Path)
res.Proxies = svr.getProxyStatsByType(proxyType) proxyInfoResp := GetProxyInfoResp{}
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
buf, _ = json.Marshal(&res)
w.Write(buf)
buf, _ := json.Marshal(&proxyInfoResp)
res.Msg = string(buf)
} }
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) { func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
@ -215,8 +212,6 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt
// Get proxy info by name. // Get proxy info by name.
type GetProxyStatsResp struct { type GetProxyStatsResp struct {
GeneralResponse
Name string `json:"name"` Name string `json:"name"`
Conf interface{} `json:"conf"` Conf interface{} `json:"conf"`
TodayTrafficIn int64 `json:"today_traffic_in"` TodayTrafficIn int64 `json:"today_traffic_in"`
@ -229,45 +224,50 @@ type GetProxyStatsResp struct {
// api/proxy/:type/:name // api/proxy/:type/:name
func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) { func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res GetProxyStatsResp
)
params := mux.Vars(r) params := mux.Vars(r)
proxyType := params["type"] proxyType := params["type"]
name := params["name"] name := params["name"]
defer func() { defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}() }()
log.Info("Http request: [%s]", r.URL.Path) log.Info("Http request: [%s]", r.URL.Path)
res = svr.getProxyStatsByTypeAndName(proxyType, name) proxyStatsResp := GetProxyStatsResp{}
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
if res.Code != 200 {
return
}
buf, _ = json.Marshal(&res) buf, _ := json.Marshal(&proxyStatsResp)
w.Write(buf) res.Msg = string(buf)
} }
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp) { func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
proxyInfo.Name = proxyName proxyInfo.Name = proxyName
ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName) ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
if ps == nil { if ps == nil {
proxyInfo.Code = 1 code = 404
proxyInfo.Msg = "no proxy info found" msg = "no proxy info found"
} else { } else {
if pxy, ok := svr.pxyManager.GetByName(proxyName); ok { if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
content, err := json.Marshal(pxy.GetConf()) content, err := json.Marshal(pxy.GetConf())
if err != nil { if err != nil {
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err) log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
proxyInfo.Code = 2 code = 400
proxyInfo.Msg = "parse conf error" msg = "parse conf error"
return return
} }
proxyInfo.Conf = getConfByType(ps.Type) proxyInfo.Conf = getConfByType(ps.Type)
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil { if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err) log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
proxyInfo.Code = 2 code = 400
proxyInfo.Msg = "parse conf error" msg = "parse conf error"
return return
} }
proxyInfo.Status = consts.Online proxyInfo.Status = consts.Online
@ -286,36 +286,38 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
// api/traffic/:name // api/traffic/:name
type GetProxyTrafficResp struct { type GetProxyTrafficResp struct {
GeneralResponse
Name string `json:"name"` Name string `json:"name"`
TrafficIn []int64 `json:"traffic_in"` TrafficIn []int64 `json:"traffic_in"`
TrafficOut []int64 `json:"traffic_out"` TrafficOut []int64 `json:"traffic_out"`
} }
func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) { func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
var ( res := GeneralResponse{Code: 200}
buf []byte
res GetProxyTrafficResp
)
params := mux.Vars(r) params := mux.Vars(r)
name := params["name"] name := params["name"]
defer func() { defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
w.Write([]byte(res.Msg))
}
}() }()
log.Info("Http request: [%s]", r.URL.Path) log.Info("Http request: [%s]", r.URL.Path)
res.Name = name trafficResp := GetProxyTrafficResp{}
trafficResp.Name = name
proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name) proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name)
if proxyTrafficInfo == nil { if proxyTrafficInfo == nil {
res.Code = 1 res.Code = 404
res.Msg = "no proxy info found" res.Msg = "no proxy info found"
return
} else { } else {
res.TrafficIn = proxyTrafficInfo.TrafficIn trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
res.TrafficOut = proxyTrafficInfo.TrafficOut trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
} }
buf, _ = json.Marshal(&res) buf, _ := json.Marshal(&trafficResp)
w.Write(buf) res.Msg = string(buf)
} }

@ -29,7 +29,7 @@ import (
) )
type HttpProxy struct { type HttpProxy struct {
BaseProxy *BaseProxy
cfg *config.HttpProxyConf cfg *config.HttpProxyConf
closeFuncs []func() closeFuncs []func()

@ -24,7 +24,7 @@ import (
) )
type HttpsProxy struct { type HttpsProxy struct {
BaseProxy *BaseProxy
cfg *config.HttpsProxyConf cfg *config.HttpsProxyConf
} }

@ -135,33 +135,33 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
case *config.TcpProxyConf: case *config.TcpProxyConf:
basePxy.usedPortsNum = 1 basePxy.usedPortsNum = 1
pxy = &TcpProxy{ pxy = &TcpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.HttpProxyConf: case *config.HttpProxyConf:
pxy = &HttpProxy{ pxy = &HttpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.HttpsProxyConf: case *config.HttpsProxyConf:
pxy = &HttpsProxy{ pxy = &HttpsProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.UdpProxyConf: case *config.UdpProxyConf:
basePxy.usedPortsNum = 1 basePxy.usedPortsNum = 1
pxy = &UdpProxy{ pxy = &UdpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.StcpProxyConf: case *config.StcpProxyConf:
pxy = &StcpProxy{ pxy = &StcpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
case *config.XtcpProxyConf: case *config.XtcpProxyConf:
pxy = &XtcpProxy{ pxy = &XtcpProxy{
BaseProxy: basePxy, BaseProxy: &basePxy,
cfg: cfg, cfg: cfg,
} }
default: default:

@ -19,7 +19,7 @@ import (
) )
type StcpProxy struct { type StcpProxy struct {
BaseProxy *BaseProxy
cfg *config.StcpProxyConf cfg *config.StcpProxyConf
} }

@ -23,7 +23,7 @@ import (
) )
type TcpProxy struct { type TcpProxy struct {
BaseProxy *BaseProxy
cfg *config.TcpProxyConf cfg *config.TcpProxyConf
realPort int realPort int

@ -30,7 +30,7 @@ import (
) )
type UdpProxy struct { type UdpProxy struct {
BaseProxy *BaseProxy
cfg *config.UdpProxyConf cfg *config.UdpProxyConf
realPort int realPort int

@ -24,7 +24,7 @@ import (
) )
type XtcpProxy struct { type XtcpProxy struct {
BaseProxy *BaseProxy
cfg *config.XtcpProxyConf cfg *config.XtcpProxyConf
closeCh chan struct{} closeCh chan struct{}

@ -89,7 +89,7 @@ func NewService() (svr *Service, err error) {
// Init group controller // Init group controller
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager) svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
// Init assets. // Init assets
err = assets.Load(cfg.AssetsDir) err = assets.Load(cfg.AssetsDir)
if err != nil { if err != nil {
err = fmt.Errorf("Load assets error: %v", err) err = fmt.Errorf("Load assets error: %v", err)

@ -20,6 +20,7 @@ import (
"github.com/fatedier/beego/logs" "github.com/fatedier/beego/logs"
) )
// Log is the under log object
var Log *logs.BeeLogger var Log *logs.BeeLogger
func init() { func init() {
@ -33,6 +34,7 @@ func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
SetLogLevel(logLevel) SetLogLevel(logLevel)
} }
// SetLogFile to configure log params
// logWay: file or console // logWay: file or console
func SetLogFile(logWay string, logFile string, maxdays int64) { func SetLogFile(logWay string, logFile string, maxdays int64) {
if logWay == "console" { if logWay == "console" {
@ -43,6 +45,7 @@ func SetLogFile(logWay string, logFile string, maxdays int64) {
} }
} }
// SetLogLevel set log level, default is warning
// value: error, warning, info, debug, trace // value: error, warning, info, debug, trace
func SetLogLevel(logLevel string) { func SetLogLevel(logLevel string) {
level := 4 // warning level := 4 // warning
@ -85,7 +88,7 @@ func Trace(format string, v ...interface{}) {
Log.Trace(format, v...) Log.Trace(format, v...)
} }
// Logger // Logger is the log interface
type Logger interface { type Logger interface {
AddLogPrefix(string) AddLogPrefix(string)
GetPrefixStr() string GetPrefixStr() string

@ -31,6 +31,7 @@ type WebsocketListener struct {
httpMutex *http.ServeMux httpMutex *http.ServeMux
} }
// NewWebsocketListener to handle websocket connections
// ln: tcp listener for websocket connections // ln: tcp listener for websocket connections
func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) { func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
wl = &WebsocketListener{ wl = &WebsocketListener{

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

14
web/frpc/.babelrc Normal file

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

6
web/frpc/.gitignore vendored Normal file

@ -0,0 +1,6 @@
.DS_Store
node_modules/
dist/
npm-debug.log
.idea
.vscode/settings.json

6
web/frpc/Makefile Normal file

@ -0,0 +1,6 @@
.PHONY: dist build
build:
@npm run build
dev:
@npm run dev

9334
web/frpc/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

46
web/frpc/package.json Normal file

@ -0,0 +1,46 @@
{
"name": "frpc-web",
"description": "An admin web ui for frp client.",
"author": "fatedier",
"private": true,
"scripts": {
"dev": "webpack-dev-server -d --inline --hot --env.dev",
"build": "rimraf dist && webpack -p --progress --hide-modules"
},
"dependencies": {
"element-ui": "^2.5.3",
"vue": "^2.5.22",
"vue-resource": "^1.5.1",
"vue-router": "^3.0.2",
"whatwg-fetch": "^3.0.0"
},
"engines": {
"node": ">=6"
},
"devDependencies": {
"autoprefixer": "^9.4.7",
"babel-core": "^6.26.3",
"babel-eslint": "^10.0.1",
"babel-loader": "^7.1.5",
"babel-plugin-component": "^1.1.1",
"babel-preset-es2015": "^6.24.1",
"css-loader": "^2.1.0",
"eslint": "^5.12.1",
"eslint-config-enough": "^0.3.4",
"eslint-loader": "^2.1.1",
"file-loader": "^3.0.1",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^2.24.1",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"postcss-loader": "^3.0.0",
"rimraf": "^2.6.3",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"vue-loader": "^15.6.2",
"vue-template-compiler": "^2.5.22",
"webpack": "^2.7.0",
"webpack-cli": "^3.2.1",
"webpack-dev-server": "^3.1.14"
}
}

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

73
web/frpc/src/App.vue Normal file

@ -0,0 +1,73 @@
<template>
<div id="app">
<header class="grid-content header-color">
<el-row>
<a class="brand" href="#">frp client</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-menu-item index="/configure">Configure</el-menu-item>
<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>
</template>
<script>
export default {
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;
}
</style>

Binary file not shown.

After

Width: 48px  |  Height: 48px  |  Size: 9.4 KiB

@ -0,0 +1,93 @@
<template>
<div>
<el-row id="head">
<el-button type="primary" @click="fetchData">Refresh</el-button>
<el-button type="primary" @click="uploadConfig">Upload</el-button>
</el-row>
<el-input type="textarea" autosize v-model="textarea" placeholder="frpc configrue file, can not be empty..."></el-input>
</div>
</template>
<script>
export default {
data() {
return {
textarea: ''
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
fetchData() {
fetch('/api/config', {credentials: 'include'})
.then(res => {
return res.text()
}).then(text => {
this.textarea= text
}).catch( err => {
this.$message({
showClose: true,
message: 'Get configure content from frpc failed!',
type: 'warning'
})
})
},
uploadConfig() {
this.$confirm('This operation will upload your frpc configure file content and hot reload it, do you want to continue?', 'Notice', {
confirmButtonText: 'Yes',
cancelButtonText: 'No',
type: 'warning'
}).then(() => {
if (this.textarea == "") {
this.$message({
type: 'warning',
message: 'Configure content can not be empty!'
})
return
}
fetch('/api/config', {
credentials: 'include',
method: 'PUT',
body: this.textarea,
}).then(() => {
fetch('/api/reload', {credentials: 'include'})
.then(() => {
this.$message({
type: 'success',
message: 'Success'
})
}).catch(err => {
this.$message({
showClose: true,
message: 'Reload frpc configure file error, ' + err,
type: 'warning'
})
})
}).catch(err => {
this.$message({
showClose: true,
message: 'Put config to frpc and hot reload failed!',
type: 'warning'
})
})
}).catch(() => {
this.$message({
type: 'info',
message: 'Canceled'
})
})
}
}
}
</script>
<style>
#head {
margin-bottom: 30px;
}
</style>

@ -0,0 +1,72 @@
<template>
<div>
<el-row>
<el-col :md="24">
<div>
<el-table :data="status" stripe style="width: 100%" :default-sort="{prop: 'type', order: 'ascending'}">
<el-table-column prop="name" label="name"></el-table-column>
<el-table-column prop="type" label="type" width="150"></el-table-column>
<el-table-column prop="local_addr" label="local address" width="200"></el-table-column>
<el-table-column prop="plugin" label="plugin" width="200"></el-table-column>
<el-table-column prop="remote_addr" label="remote address"></el-table-column>
<el-table-column prop="status" label="status" width="150"></el-table-column>
<el-table-column prop="err" label="info"></el-table-column>
</el-table>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
data() {
return {
status: null
}
},
created() {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
fetchData() {
fetch('/api/status', {credentials: 'include'})
.then(res => {
return res.json()
}).then(json => {
this.status = new Array()
for (let s of json.tcp) {
this.status.push(s)
}
for (let s of json.udp) {
this.status.push(s)
}
for (let s of json.http) {
this.status.push(s)
}
for (let s of json.https) {
this.status.push(s)
}
for (let s of json.stcp) {
this.status.push(s)
}
for (let s of json.xtcp) {
this.status.push(s)
}
}).catch( err => {
this.$message({
showClose: true,
message: 'Get status info from frpc failed!',
type: 'warning'
})
})
}
}
}
</script>
<style>
</style>

15
web/frpc/src/index.html Normal file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>frp client admin UI</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>

52
web/frpc/src/main.js Normal file

@ -0,0 +1,52 @@
import Vue from 'vue'
// import ElementUI from 'element-ui'
import {
Button,
Form,
FormItem,
Row,
Col,
Table,
TableColumn,
Menu,
MenuItem,
MessageBox,
Message,
Input
} 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'
import './utils/less/custom.less'
import App from './App.vue'
import router from './router'
import 'whatwg-fetch'
locale.use(lang)
Vue.use(Button)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Row)
Vue.use(Col)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(Menu)
Vue.use(MenuItem)
Vue.use(Input)
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$message = Message
//Vue.use(ElementUI)
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})

@ -0,0 +1,18 @@
import Vue from 'vue'
import Router from 'vue-router'
import Overview from '../components/Overview.vue'
import Configure from '../components/Configure.vue'
Vue.use(Router)
export default new Router({
routes: [{
path: '/',
name: 'Overview',
component: Overview
},{
path: '/configure',
name: 'Configure',
component: Configure,
}]
})

@ -0,0 +1,22 @@
@color: red;
.el-form-item {
span {
margin-left: 15px;
}
}
.demo-table-expand {
font-size: 0;
label {
width: 90px;
color: #99a9bf;
}
.el-form-item {
margin-right: 0;
margin-bottom: 0;
width: 50%;
}
}

@ -0,0 +1,13 @@
class ProxyStatus {
constructor(status) {
this.name = status.name
this.type = status.type
this.status = status.status
this.err = status.err
this.local_addr = status.local_addr
this.plugin = status.plugin
this.remote_addr = status.remote_addr
}
}
export {ProxyStatus}

107
web/frpc/webpack.config.js Normal file

@ -0,0 +1,107 @@
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'
})

6236
web/frpc/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

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