From d8683a00797e5e209b8d074a4af94e0d3099c17d Mon Sep 17 00:00:00 2001 From: fatedier Date: Mon, 27 Mar 2017 02:15:31 +0800 Subject: [PATCH] new frps dashboard --- web/frps/.babelrc | 5 + web/frps/.gitignore | 6 + web/frps/Makefile | 9 ++ web/frps/package.json | 46 ++++++ web/frps/postcss.config.js | 5 + web/frps/src/App.vue | 78 ++++++++++ web/frps/src/assets/favicon.ico | Bin 0 -> 9662 bytes web/frps/src/components/Overview.vue | 140 +++++++++++++++++ web/frps/src/components/ProxiesHttp.vue | 142 +++++++++++++++++ web/frps/src/components/ProxiesHttps.vue | 137 +++++++++++++++++ web/frps/src/components/ProxiesTcp.vue | 118 ++++++++++++++ web/frps/src/components/ProxiesUdp.vue | 120 +++++++++++++++ web/frps/src/components/Traffic.vue | 36 +++++ web/frps/src/index.html | 15 ++ web/frps/src/main.js | 17 +++ web/frps/src/router/index.js | 33 ++++ web/frps/src/utils/chart.js | 187 +++++++++++++++++++++++ web/frps/src/utils/less/custom.less | 22 +++ web/frps/src/utils/proxy.js | 88 +++++++++++ web/frps/src/vendor.js | 2 + web/frps/webpack.config.js | 93 +++++++++++ 21 files changed, 1299 insertions(+) create mode 100644 web/frps/.babelrc create mode 100644 web/frps/.gitignore create mode 100644 web/frps/Makefile create mode 100644 web/frps/package.json create mode 100644 web/frps/postcss.config.js create mode 100644 web/frps/src/App.vue create mode 100644 web/frps/src/assets/favicon.ico create mode 100644 web/frps/src/components/Overview.vue create mode 100644 web/frps/src/components/ProxiesHttp.vue create mode 100644 web/frps/src/components/ProxiesHttps.vue create mode 100644 web/frps/src/components/ProxiesTcp.vue create mode 100644 web/frps/src/components/ProxiesUdp.vue create mode 100644 web/frps/src/components/Traffic.vue create mode 100644 web/frps/src/index.html create mode 100644 web/frps/src/main.js create mode 100644 web/frps/src/router/index.js create mode 100644 web/frps/src/utils/chart.js create mode 100644 web/frps/src/utils/less/custom.less create mode 100644 web/frps/src/utils/proxy.js create mode 100644 web/frps/src/vendor.js create mode 100644 web/frps/webpack.config.js diff --git a/web/frps/.babelrc b/web/frps/.babelrc new file mode 100644 index 0000000..5eae2e8 --- /dev/null +++ b/web/frps/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + ["es2015", { "modules": false }] + ] +} \ No newline at end of file diff --git a/web/frps/.gitignore b/web/frps/.gitignore new file mode 100644 index 0000000..3cd34b4 --- /dev/null +++ b/web/frps/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +node_modules/ +dist/ +npm-debug.log +.idea +.vscode/settings.json diff --git a/web/frps/Makefile b/web/frps/Makefile new file mode 100644 index 0000000..42dec29 --- /dev/null +++ b/web/frps/Makefile @@ -0,0 +1,9 @@ +.PHONY: dist build +install: + @npm install + +dev: install + @npm run dev + +build: + @npm run build \ No newline at end of file diff --git a/web/frps/package.json b/web/frps/package.json new file mode 100644 index 0000000..9d41c4c --- /dev/null +++ b/web/frps/package.json @@ -0,0 +1,46 @@ +{ + "name": "frps-dashboard", + "description": "A dashboard for frp server.", + "author": "fatedier", + "private": true, + "scripts": { + "dev": "webpack-dev-server -d --inline --hot --env.dev", + "build": "rimraf dist && webpack -p --progress --hide-modules" + }, + "dependencies": { + "bootstrap": "^3.3.7", + "echarts": "^3.5.0", + "element-ui": "^1.2.5", + "humanize-plus": "^1.8.2", + "vue": "^2.2.4", + "vue-resource": "^1.2.1", + "vue-router": "^2.3.0" + }, + "engines": { + "node": ">=6" + }, + "devDependencies": { + "autoprefixer": "^6.6.0", + "babel-core": "^6.21.0", + "babel-eslint": "^7.1.1", + "babel-loader": "^6.4.0", + "babel-preset-es2015": "^6.13.2", + "css-loader": "^0.27.0", + "eslint": "^3.12.2", + "eslint-config-enough": "^0.2.2", + "eslint-loader": "^1.6.3", + "file-loader": "^0.10.1", + "html-loader": "^0.4.5", + "html-webpack-plugin": "^2.24.1", + "less": "^2.7.2", + "less-loader": "^3.0.0", + "postcss-loader": "^1.3.3", + "rimraf": "^2.5.4", + "style-loader": "^0.13.2", + "url-loader": "^0.5.8", + "vue-loader": "^11.1.4", + "vue-template-compiler": "^2.1.8", + "webpack": "^2.2.0-rc.4", + "webpack-dev-server": "beta" + } +} diff --git a/web/frps/postcss.config.js b/web/frps/postcss.config.js new file mode 100644 index 0000000..af65640 --- /dev/null +++ b/web/frps/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: [ + require('autoprefixer')() + ] +} \ No newline at end of file diff --git a/web/frps/src/App.vue b/web/frps/src/App.vue new file mode 100644 index 0000000..070e781 --- /dev/null +++ b/web/frps/src/App.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/web/frps/src/assets/favicon.ico b/web/frps/src/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4347765571a827fe6bcc4931a4ff925d07574299 GIT binary patch literal 9662 zcmdT~TWDNG7@pb(Auqo9keB8~kPvK=YE@K}K!3-?w{ab9VQfIcGO}X(s$TIdkU! z=ld@+^Uv%mwGDs0y$YXQ>clllU9FV57KA4C5Qy!~ETKT}6`|qYF3=!o2s8njt{}GY z9s6{VrEdjR9kfNH_)rW<8Y)+UbxxSaJk;`x1C)wzKl3TsxOtDK6`}lbYJ-4$J zIuM)V{$TB^j&YNk|58jPyR;yoksDfppJPlr?bB!<06FZdJ0*Jb36Xr>T?M?0zV~#i zd_ar?_BgU^&m6?rhWmC&=HhSe`0*ya@QpLCo6OC%&)9tc+{`20b1}qrY--quKcCCW z%HaoFr310{XdmEz=CS6nM{kmB_%EXl$)7)KRSXg92=RxCdHk#=e)wQ*dfbRVpI(>9 z_@LPiUUu^Te%8XybCY}Q*yM#@x?OVdWup#>Ge=tG17g-^K7TfY4-#J=H{#F5BNBf3 z4uA4&w(<18bKgPm4b{TC{Kmb)I5Cy@sl)KWbwxjLU(@7_VKcfC`$R4Hi6!~dCnork zPsJtk&o5<^I`?@q_Bdj9+;dGjtsHtl^0|yr2d9^-?+-P4ZV-FT_^Dg^&$CVtjcu6= z=On~@uyJuzAAZEp>hWjHIfauZ`J6;29|Qh|<$Cqur~UR_FPVSmoKPCuQojcq8AHTh z+x*qOhYw;?0V7tv%trn$o5$(JA0={dZ=+&}*oW$k$MD!KlEc}+RMx-y0QRm}OSKI) zUlTj`fcoqKAH=_S+XP?o$u)_*Hc%x$a^TK_ALt7wD(-=v^Mu%a=MPSLMqb?uj4`7Q ziPID1F%P{{bEgYyFxO!x$}atZIku}xZySbU{!qL-_Rinje1zCoKYP-MKNky0_=Vdx zumHbjE{J?Q^7=g+Jk{eD^0z_`>L-7$<}bjCBt~&J%%NV*t|+~^3hU%&1cnR=NHfOqbGhJXJmj~J@IUD9w2_! zzn?O~=eibtagQ7Rvh$lFy`YcK%i3!9%69P3)sl#G8BmluN8g z^xa3w=lq5ZtIqGlZ~1`xnVZh$cGxs}^hsHtJt@qe*FHTU(Ibyb_?5fB*C*kzTMN&r zxl>qga+PWpa*^EXQh`4*j(H1tE?6AzPb?o)-Cw!LaF&fRBM0x6<+tvam3JPN*oPyM zI5SyTD>Jww$}V37U65>OQPPV)2y=H@ zYw6I5zVo1Z#%Tr*vG}?_Cw><6?*=Vg209Q!P2XP>H7M!OPCsz$y0F`H=!A^vHu!;K z))X@ub7c)VoYPzK`%}9v&|yn{e{I*n+NYUo%z%8|JEEUyO`;6SYWnX79Q0t`x}|=< zVb?+b-_GC9h$MH2C{?1YQhWHkqSRHagGy~@9l-C1SQ9ip@~nrILU%AIm1Mm`DX@WY zqdw2)E0xl9kk8wd(sh8(+m!OE*{zlmU}qn4gY0L+bDqyOyp{TBJ+ +
+ + +
+ + + {{ vhost_http_port }} + + + {{ vhost_https_port }} + + + {{ auth_timeout }} + + + {{ subdomain_host }} + + + {{ max_pool_count }} + + + {{ heart_beat_timeout }} + + + {{ client_counts }} + + + {{ cur_conns }} + + + {{ proxy_counts }} + + +
+
+ +
+
+
+
+
+ + + + + diff --git a/web/frps/src/components/ProxiesHttp.vue b/web/frps/src/components/ProxiesHttp.vue new file mode 100644 index 0000000..5aa7691 --- /dev/null +++ b/web/frps/src/components/ProxiesHttp.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/web/frps/src/components/ProxiesHttps.vue b/web/frps/src/components/ProxiesHttps.vue new file mode 100644 index 0000000..bd8bc55 --- /dev/null +++ b/web/frps/src/components/ProxiesHttps.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/web/frps/src/components/ProxiesTcp.vue b/web/frps/src/components/ProxiesTcp.vue new file mode 100644 index 0000000..bf4d220 --- /dev/null +++ b/web/frps/src/components/ProxiesTcp.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/web/frps/src/components/ProxiesUdp.vue b/web/frps/src/components/ProxiesUdp.vue new file mode 100644 index 0000000..2b46860 --- /dev/null +++ b/web/frps/src/components/ProxiesUdp.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/web/frps/src/components/Traffic.vue b/web/frps/src/components/Traffic.vue new file mode 100644 index 0000000..ae57364 --- /dev/null +++ b/web/frps/src/components/Traffic.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/web/frps/src/index.html b/web/frps/src/index.html new file mode 100644 index 0000000..aebd655 --- /dev/null +++ b/web/frps/src/index.html @@ -0,0 +1,15 @@ + + + + + + frps dashboard + + + +
+ + + + + diff --git a/web/frps/src/main.js b/web/frps/src/main.js new file mode 100644 index 0000000..d41949e --- /dev/null +++ b/web/frps/src/main.js @@ -0,0 +1,17 @@ +import Vue from 'vue' +import ElementUI from 'element-ui' +import 'element-ui/lib/theme-default/index.css' +import './utils/less/custom.less' + +import App from './App.vue' +import router from './router' + +Vue.use(ElementUI) +Vue.config.productionTip = false + +new Vue({ + el: '#app', + router, + template: '', + components: { App } +}) diff --git a/web/frps/src/router/index.js b/web/frps/src/router/index.js new file mode 100644 index 0000000..bd77e97 --- /dev/null +++ b/web/frps/src/router/index.js @@ -0,0 +1,33 @@ +import Vue from 'vue' +import Router from 'vue-router' +import Overview from '../components/Overview.vue' +import ProxiesTcp from '../components/ProxiesTcp.vue' +import ProxiesUdp from '../components/ProxiesUdp.vue' +import ProxiesHttp from '../components/ProxiesHttp.vue' +import ProxiesHttps from '../components/ProxiesHttps.vue' + +Vue.use(Router) + +export default new Router({ + routes: [{ + path: '/', + name: 'Overview', + component: Overview + }, { + path: '/proxies/tcp', + name: 'ProxiesTcp', + component: ProxiesTcp + }, { + path: '/proxies/udp', + name: 'ProxiesUdp', + component: ProxiesUdp + }, { + path: '/proxies/http', + name: 'ProxiesHttp', + component: ProxiesHttp + }, { + path: '/proxies/https', + name: 'ProxiesHttps', + component: ProxiesHttps + }] +}) \ No newline at end of file diff --git a/web/frps/src/utils/chart.js b/web/frps/src/utils/chart.js new file mode 100644 index 0000000..82d45f7 --- /dev/null +++ b/web/frps/src/utils/chart.js @@ -0,0 +1,187 @@ +import Humanize from "humanize-plus" +import echarts from "echarts/lib/echarts" + +import "echarts/theme/macarons" +import "echarts/lib/chart/bar" +import "echarts/lib/chart/pie" +import "echarts/lib/component/tooltip" +import "echarts/lib/component/title" + +function DrawTrafficChart(elementId, trafficIn, trafficOut) { + let myChart = echarts.init(document.getElementById(elementId), 'macarons'); + myChart.showLoading() + + let option = { + title: { + text: 'Network Traffic', + subtext: 'today', + x: 'center' + }, + tooltip: { + trigger: 'item', + formatter: function(v) { + return Humanize.fileSize(v.data.value) + " (" + v.percent + "%)" + } + }, + series: [{ + type: 'pie', + radius: '55%', + center: ['50%', '60%'], + data: [{ + value: trafficIn, + name: 'Traffic In' + }, { + value: trafficOut, + name: 'Traffic Out' + }, ], + itemStyle: { + emphasis: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)' + } + } + }] + }; + myChart.setOption(option); + myChart.hideLoading() +} + +function DrawProxyChart(elementId, serverInfo) { + if (serverInfo.proxy_type_count.tcp == null) { + serverInfo.proxy_type_count.tcp = 0 + } + if (serverInfo.proxy_type_count.udp == null) { + serverInfo.proxy_type_count.udp = 0 + } + if (serverInfo.proxy_type_count.http == null) { + serverInfo.proxy_type_count.http = 0 + } + if (serverInfo.proxy_type_count.https == null) { + serverInfo.proxy_type_count.https = 0 + } + let myChart = echarts.init(document.getElementById(elementId), 'macarons') + myChart.showLoading() + + let option = { + title: { + text: 'Proxies', + subtext: 'now', + x: 'center' + }, + tooltip: { + trigger: 'item', + formatter: function(v) { + return v.data.value + } + }, + series: [{ + type: 'pie', + radius: '55%', + center: ['50%', '60%'], + data: [{ + value: serverInfo.proxy_type_count.tcp, + name: 'TCP' + }, { + value: serverInfo.proxy_type_count.udp, + name: 'UDP' + }, { + value: serverInfo.proxy_type_count.http, + name: 'HTTP' + }, { + value: serverInfo.proxy_type_count.https, + name: 'HTTPS' + }], + itemStyle: { + emphasis: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)' + } + } + }] + }; + myChart.setOption(option); + myChart.hideLoading() +} + +// 7 days +function DrawProxyTrafficChart(elementId, trafficInArr, trafficOutArr) { + let params = { + width: '600px', + height: '400px' + } + + let myChart = echarts.init(document.getElementById(elementId), 'macarons', params); + myChart.showLoading() + + trafficInArr = trafficInArr.reverse() + trafficOutArr = trafficOutArr.reverse() + let now = new Date() + now = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6) + let dates = new Array() + for (let i = 0; i < 7; i++) { + dates.push(now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate()) + now = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1) + } + + let option = { + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + }, + formatter: function(data) { + let html = '' + if (data.length > 0) { + html += data[0].name + '
' + } + for (let v of data) { + let colorEl = ''; + html += colorEl + v.seriesName + ': ' + Humanize.fileSize(v.value) + '
' + } + return html + } + }, + legend: { + data: ['Traffic In', 'Traffic Out'] + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + containLabel: true + }, + xAxis: [{ + type: 'category', + data: dates + }], + yAxis: [{ + type: 'value', + axisLabel: { + formatter: function(value) { + return Humanize.fileSize(value) + } + } + }], + series: [{ + name: 'Traffic In', + type: 'bar', + data: trafficInArr + }, { + + name: 'Traffic Out', + type: 'bar', + data: trafficOutArr + }] + }; + myChart.setOption(option); + myChart.hideLoading() +} + +export { + DrawTrafficChart, + DrawProxyChart, + DrawProxyTrafficChart +} diff --git a/web/frps/src/utils/less/custom.less b/web/frps/src/utils/less/custom.less new file mode 100644 index 0000000..b83a400 --- /dev/null +++ b/web/frps/src/utils/less/custom.less @@ -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%; + } +} diff --git a/web/frps/src/utils/proxy.js b/web/frps/src/utils/proxy.js new file mode 100644 index 0000000..5f2bbee --- /dev/null +++ b/web/frps/src/utils/proxy.js @@ -0,0 +1,88 @@ +class BaseProxy { + constructor(proxyStats) { + this.name = proxyStats.name + if (proxyStats.conf != null) { + this.encryption = proxyStats.conf.use_encryption + this.compression = proxyStats.conf.use_compression + } else { + this.encryption = "" + this.compression = "" + } + this.conns = proxyStats.cur_conns + this.traffic_in = proxyStats.today_traffic_in + this.traffic_out = proxyStats.today_traffic_out + this.status = proxyStats.status + } +} + +class TcpProxy extends BaseProxy { + constructor(proxyStats) { + super(proxyStats) + this.type = "tcp" + if (proxyStats.conf != null) { + this.addr = proxyStats.conf.bind_addr + ":" + proxyStats.conf.remote_port + this.port = proxyStats.conf.remote_port + } else { + this.addr = "" + this.port = "" + } + } +} + +class UdpProxy extends BaseProxy { + constructor(proxyStats) { + super(proxyStats) + this.type = "udp" + if (proxyStats.conf != null) { + this.addr = proxyStats.conf.bind_addr + ":" + proxyStats.conf.remote_port + this.port = proxyStats.conf.remote_port + } else { + this.addr = "" + this.port = "" + } + } +} + +class HttpProxy extends BaseProxy { + constructor(proxyStats, port, subdomain_host) { + super(proxyStats) + this.type = "http" + this.port = port + if (proxyStats.conf != null) { + this.custom_domains = proxyStats.conf.custom_domains + this.host_header_rewrite = proxyStats.conf.host_header_rewrite + this.locations = proxyStats.conf.locations + if (proxyStats.conf.sub_domain != "") { + this.subdomain = proxyStats.conf.sub_domain + "." + subdomain_host + } else { + this.subdomain = "" + } + } else { + this.custom_domains = "" + this.host_header_rewrite = "" + this.subdomain = "" + this.locations = "" + } + } +} + +class HttpsProxy extends BaseProxy { + constructor(proxyStats, port, subdomain_host) { + super(proxyStats) + this.type = "https" + this.port = port + if (proxyStats.conf != null) { + this.custom_domains = proxyStats.conf.custom_domains + if (proxyStats.conf.sub_domain != "") { + this.subdomain = proxyStats.conf.sub_domain + "." + subdomain_host + } else { + this.subdomain = "" + } + } else { + this.custom_domains = "" + this.subdomain = "" + } + } +} + +export {BaseProxy, TcpProxy, UdpProxy, HttpProxy, HttpsProxy} diff --git a/web/frps/src/vendor.js b/web/frps/src/vendor.js new file mode 100644 index 0000000..986e5d0 --- /dev/null +++ b/web/frps/src/vendor.js @@ -0,0 +1,2 @@ +import Vue from 'vue' +import ElementUI from 'element-ui' \ No newline at end of file diff --git a/web/frps/webpack.config.js b/web/frps/webpack.config.js new file mode 100644 index 0000000..74503ab --- /dev/null +++ b/web/frps/webpack.config.js @@ -0,0 +1,93 @@ +const path = require('path') +var webpack = require('webpack') +var HtmlWebpackPlugin = require('html-webpack-plugin') +var url = require('url') +var publicPath = '' + +module.exports = (options = {}) => ({ + entry: { + vendor: './src/vendor', + index: './src/main.js' + }, + 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$/, + use: ['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' + }) + ], + 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' +})