mirror of
https://github.com/fatedier/frp.git
synced 2024-12-13 10:11:04 +01:00
commit
1723d7b651
12
Makefile
12
Makefile
@ -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: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
1
assets/frpc/static/index.html
Normal file
1
assets/frpc/static/index.html
Normal file
@ -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>
|
1
assets/frpc/static/manifest.js
Normal file
1
assets/frpc/static/manifest.js
Normal file
@ -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}}([]);
|
1
assets/frpc/static/vendor.js
Normal file
1
assets/frpc/static/vendor.js
Normal file
File diff suppressed because one or more lines are too long
10
assets/frpc/statik/statik.go
Normal file
10
assets/frpc/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
BIN
assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
Binary file not shown.
BIN
assets/frps/static/favicon.ico
Normal file
BIN
assets/frps/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
10
assets/frps/statik/statik.go
Normal file
10
assets/frps/statik/statik.go
Normal file
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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() {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
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
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
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
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
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"
|
||||||
|
}
|
||||||
|
}
|
5
web/frpc/postcss.config.js
Normal file
5
web/frpc/postcss.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
require('autoprefixer')()
|
||||||
|
]
|
||||||
|
}
|
73
web/frpc/src/App.vue
Normal file
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>
|
BIN
web/frpc/src/assets/favicon.ico
Normal file
BIN
web/frpc/src/assets/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
93
web/frpc/src/components/Configure.vue
Normal file
93
web/frpc/src/components/Configure.vue
Normal file
@ -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>
|
72
web/frpc/src/components/Overview.vue
Normal file
72
web/frpc/src/components/Overview.vue
Normal file
@ -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
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
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 }
|
||||||
|
})
|
18
web/frpc/src/router/index.js
Normal file
18
web/frpc/src/router/index.js
Normal file
@ -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,
|
||||||
|
}]
|
||||||
|
})
|
22
web/frpc/src/utils/less/custom.less
Normal file
22
web/frpc/src/utils/less/custom.less
Normal file
@ -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%;
|
||||||
|
}
|
||||||
|
}
|
13
web/frpc/src/utils/status.js
Normal file
13
web/frpc/src/utils/status.js
Normal file
@ -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
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
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
|
build:
|
||||||
|
@npm run build
|
||||||
|
|
||||||
dev: install
|
dev: install
|
||||||
@npm run dev
|
@npm run dev
|
||||||
|
|
||||||
build:
|
|
||||||
@npm run build
|
|
Loading…
Reference in New Issue
Block a user