mirror of https://github.com/fatedier/frp
add admin UI for frpc
parent
d879b8208b
commit
96d7e2da6f
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 |
|
@ -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?eb6e6e7683a17c61011d"></script><script type="text/javascript" src="vendor.js?1fbc6539feeed727105b"></script></body> </html>
|
|
@ -1 +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:"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}}([]);
|
!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:"1fbc6539feeed727105b"}[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: | Height: | Size: 9.4 KiB |
|
@ -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> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?bc42bc4eff72df8da372"></script><script type="text/javascript" src="vendor.js?ee403fce53c8757fc931"></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:"ee403fce53c8757fc931"}[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
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"
|
||||||
|
|
||||||
|
@ -44,6 +45,13 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
|
||||||
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
||||||
router.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
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{
|
||||||
Addr: address,
|
Addr: address,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
["es2015", { "modules": false }]
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"component",
|
||||||
|
{
|
||||||
|
"libraryName": "element-ui",
|
||||||
|
"styleLibraryName": "theme-chalk"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
npm-debug.log
|
||||||
|
.idea
|
||||||
|
.vscode/settings.json
|
|
@ -0,0 +1,6 @@
|
||||||
|
.PHONY: dist build
|
||||||
|
build:
|
||||||
|
@npm run build
|
||||||
|
|
||||||
|
dev:
|
||||||
|
@npm run dev
|
File diff suppressed because it is too large
Load Diff
|
@ -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')()
|
||||||
|
]
|
||||||
|
}
|
|
@ -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: | Height: | Size: 9.4 KiB |
|
@ -0,0 +1,106 @@
|
||||||
|
<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(res => {
|
||||||
|
return res.json()
|
||||||
|
}).then(json => {
|
||||||
|
console.log(json)
|
||||||
|
if (json.code != 0) {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: 'Put config to frpc and hot reload failed!',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
fetch('/api/reload', {credentials: 'include'})
|
||||||
|
.then(res => {
|
||||||
|
return res.json()
|
||||||
|
}).then(json => {
|
||||||
|
this.$message({
|
||||||
|
type: 'success',
|
||||||
|
message: 'Success'
|
||||||
|
})
|
||||||
|
}).catch(err => {
|
||||||
|
this.$message({
|
||||||
|
showClose: true,
|
||||||
|
message: 'Reload frpc configure file error!',
|
||||||
|
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>
|
|
@ -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>
|
|
@ -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}
|
|
@ -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'
|
||||||
|
})
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue