mirror of https://github.com/fatedier/frp
fatedier
8 years ago
21 changed files with 1299 additions and 0 deletions
@ -0,0 +1,5 @@
|
||||
{ |
||||
"presets": [ |
||||
["es2015", { "modules": false }] |
||||
] |
||||
} |
@ -0,0 +1,6 @@
|
||||
.DS_Store |
||||
node_modules/ |
||||
dist/ |
||||
npm-debug.log |
||||
.idea |
||||
.vscode/settings.json |
@ -0,0 +1,9 @@
|
||||
.PHONY: dist build |
||||
install: |
||||
@npm install
|
||||
|
||||
dev: install |
||||
@npm run dev
|
||||
|
||||
build: |
||||
@npm run build
|
@ -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" |
||||
} |
||||
} |
@ -0,0 +1,5 @@
|
||||
module.exports = { |
||||
plugins: [ |
||||
require('autoprefixer')() |
||||
] |
||||
} |
@ -0,0 +1,78 @@
|
||||
<template> |
||||
<div id="app"> |
||||
<header class="grid-content header-color"> |
||||
<el-row> |
||||
<a class="brand" href="#">frp</a> |
||||
</el-row> |
||||
</header> |
||||
<section> |
||||
<el-row :gutter="20"> |
||||
<el-col id="side-nav" :xs="24" :md="4"> |
||||
<el-menu default-active="1" mode="vertical" theme="light" router="false" @select="handleSelect"> |
||||
<el-menu-item index="/">Overview</el-menu-item> |
||||
<el-submenu index="/proxies"> |
||||
<template slot="title">Proxies</template> |
||||
<el-menu-item index="/proxies/tcp">TCP</el-menu-item> |
||||
<el-menu-item index="/proxies/udp">UDP</el-menu-item> |
||||
<el-menu-item index="/proxies/http">HTTP</el-menu-item> |
||||
<el-menu-item index="/proxies/https">HTTPS</el-menu-item> |
||||
</el-submenu> |
||||
<el-menu-item index="">Help</el-menu-item> |
||||
</el-menu> |
||||
</el-col> |
||||
|
||||
<el-col :xs="24" :md="20"> |
||||
<div id="content"> |
||||
<router-view></router-view> |
||||
</div> |
||||
</el-col> |
||||
</el-row> |
||||
</section> |
||||
<footer></footer> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
methods: { |
||||
handleSelect(key, path) { |
||||
if (key == '') { |
||||
window.open("http://github.com/fatedier/frp") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
body { |
||||
background-color: #fafafa; |
||||
margin: 0px; |
||||
} |
||||
|
||||
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> |
After Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1,140 @@
|
||||
<template> |
||||
<div> |
||||
<el-row> |
||||
<el-col :md="12"> |
||||
<div class="source"> |
||||
<el-form label-position="left" class="server_info"> |
||||
<el-form-item label="Http Port"> |
||||
<span>{{ vhost_http_port }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Https Port"> |
||||
<span>{{ vhost_https_port }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Auth Timeout"> |
||||
<span>{{ auth_timeout }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Subdomain Host"> |
||||
<span>{{ subdomain_host }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Max PoolCount"> |
||||
<span>{{ max_pool_count }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="HeartBeat Timeout"> |
||||
<span>{{ heart_beat_timeout }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Client Counts"> |
||||
<span>{{ client_counts }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Current Conns"> |
||||
<span>{{ cur_conns }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Proxy Counts"> |
||||
<span>{{ proxy_counts }}</span> |
||||
</el-form-item> |
||||
</el-form> |
||||
</div> |
||||
</el-col> |
||||
<el-col :md="12"> |
||||
<div id="traffic" style="width: 400px;height:250px;margin-bottom: 30px;"></div> |
||||
<div id="proxies" style="width: 400px;height:250px;"></div> |
||||
</el-col> |
||||
</el-row> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import {DrawTrafficChart, DrawProxyChart} from "../utils/chart.js" |
||||
|
||||
export default { |
||||
data() { |
||||
return { |
||||
vhost_http_port: "", |
||||
vhost_https_port: "", |
||||
auth_timeout: "", |
||||
subdomain_host: "", |
||||
max_pool_count: "", |
||||
heart_beat_timeout: "", |
||||
client_counts: "", |
||||
cur_conns: "", |
||||
proxy_counts: "" |
||||
} |
||||
}, |
||||
created() { |
||||
this.fetchData() |
||||
}, |
||||
watch: { |
||||
'$route': 'fetchData' |
||||
}, |
||||
methods: { |
||||
fetchData() { |
||||
fetch('/api/serverinfo') |
||||
.then(res => { |
||||
return res.json() |
||||
}).then(json => { |
||||
this.vhost_http_port = json.vhost_http_port |
||||
if (this.vhost_http_port == 0) { |
||||
this.vhost_http_port = "disable" |
||||
} |
||||
this.vhost_https_port = json.vhost_https_port |
||||
if (this.vhost_https_port == 0) { |
||||
this.vhost_https_port = "disable" |
||||
} |
||||
this.auth_timeout = json.auth_timeout |
||||
this.subdomain_host = json.subdomain_host |
||||
this.max_pool_count = json.max_pool_count |
||||
this.heart_beat_timeout = json.heart_beat_timeout |
||||
this.client_counts = json.client_counts |
||||
this.cur_conns = json.cur_conns |
||||
this.proxy_counts = 0 |
||||
if (json.proxy_type_count != null) { |
||||
if (json.proxy_type_count.tcp != null) { |
||||
this.proxy_counts += json.proxy_type_count.tcp |
||||
} |
||||
if (json.proxy_type_count.udp != null) { |
||||
this.proxy_counts += json.proxy_type_count.udp |
||||
} |
||||
if (json.proxy_type_count.http != null) { |
||||
this.proxy_counts += json.proxy_type_count.http |
||||
} |
||||
if (json.proxy_type_count.https != null) { |
||||
this.proxy_counts += json.proxy_type_count.https |
||||
} |
||||
} |
||||
DrawTrafficChart('traffic', json.total_traffic_in, json.total_traffic_out) |
||||
DrawProxyChart('proxies', json) |
||||
}).catch( err => { |
||||
this.$message({ |
||||
showClose: true, |
||||
message: 'Get server info from frps failed!', |
||||
type: 'warning' |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
.source { |
||||
border: 1px solid #eaeefb; |
||||
border-radius: 4px; |
||||
transition: .2s; |
||||
padding: 24px; |
||||
} |
||||
|
||||
.server_info { |
||||
margin-left: 40px; |
||||
font-size: 0px; |
||||
} |
||||
|
||||
.server_info label { |
||||
width: 150px; |
||||
color: #99a9bf; |
||||
} |
||||
|
||||
.server_info .el-form-item { |
||||
margin-right: 0; |
||||
margin-bottom: 0; |
||||
width: 100%; |
||||
} |
||||
</style> |
@ -0,0 +1,142 @@
|
||||
<template> |
||||
<div> |
||||
<el-table :data="proxies" :default-sort="{prop: 'name', order: 'ascending'}" style="width: 100%"> |
||||
<el-table-column type="expand"> |
||||
<template scope="props"> |
||||
<el-popover |
||||
ref="popover4" |
||||
placement="right" |
||||
width="600" |
||||
style="margin-left:0px" |
||||
trigger="click"> |
||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart> |
||||
</el-popover> |
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom:10px">Traffic Statistics</el-button> |
||||
|
||||
<el-form label-position="left" inline class="demo-table-expand"> |
||||
<el-form-item label="Name"> |
||||
<span>{{ props.row.name }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Type"> |
||||
<span>{{ props.row.type }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Domains"> |
||||
<span>{{ props.row.custom_domains }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="SubDomain"> |
||||
<span>{{ props.row.subdomain }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="locations"> |
||||
<span>{{ props.row.locations }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="HostRewrite"> |
||||
<span>{{ props.row.host_header_rewrite }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Encryption"> |
||||
<span>{{ props.row.encryption }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Compression"> |
||||
<span>{{ props.row.compression }}</span> |
||||
</el-form-item> |
||||
</el-form> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Name" |
||||
prop="name" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Port" |
||||
prop="port" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Connections" |
||||
prop="conns" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Traffic In" |
||||
prop="traffic_in" |
||||
:formatter="formatTrafficIn" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Traffic Out" |
||||
prop="traffic_out" |
||||
:formatter="formatTrafficOut" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="status" |
||||
prop="status" |
||||
sortable> |
||||
<template scope="scope"> |
||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag> |
||||
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import Humanize from "humanize-plus"; |
||||
import Traffic from './Traffic.vue' |
||||
import { |
||||
HttpProxy |
||||
} from "../utils/proxy.js" |
||||
export default { |
||||
data() { |
||||
return { |
||||
proxies: null, |
||||
vhost_http_port: "", |
||||
subdomain_host: "" |
||||
} |
||||
}, |
||||
created() { |
||||
this.fetchData() |
||||
}, |
||||
watch: { |
||||
'$route': 'fetchData' |
||||
}, |
||||
methods: { |
||||
formatTrafficIn(row, column) { |
||||
return Humanize.fileSize(row.traffic_in) |
||||
}, |
||||
formatTrafficOut(row, column) { |
||||
return Humanize.fileSize(row.traffic_out) |
||||
}, |
||||
fetchData() { |
||||
fetch('/api/serverinfo') |
||||
.then(res => { |
||||
return res.json() |
||||
}).then(json => { |
||||
this.vhost_http_port = json.vhost_http_port |
||||
this.subdomain_host = json.subdomain_host |
||||
if (this.vhost_http_port == null || this.vhost_http_port == 0) { |
||||
return |
||||
} else { |
||||
fetch('/api/proxy/http') |
||||
.then(res => { |
||||
return res.json() |
||||
}).then(json => { |
||||
this.proxies = new Array() |
||||
for (let proxyStats of json.proxies) { |
||||
this.proxies.push(new HttpProxy(proxyStats, this.vhost_http_port, this.subdomain_host)) |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
}, |
||||
components: { |
||||
'my-traffic-chart': Traffic |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
</style> |
@ -0,0 +1,137 @@
|
||||
<template> |
||||
<div> |
||||
<el-table :data="proxies" :default-sort="{prop: 'name', order: 'ascending'}" style="width: 100%"> |
||||
<el-table-column type="expand"> |
||||
<template scope="props"> |
||||
<el-popover |
||||
ref="popover4" |
||||
placement="right" |
||||
width="600" |
||||
style="margin-left:0px" |
||||
trigger="click"> |
||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart> |
||||
</el-popover> |
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom:10px">Traffic Statistics</el-button> |
||||
|
||||
<el-form label-position="left" inline class="demo-table-expand"> |
||||
<el-form-item label="Name"> |
||||
<span>{{ props.row.name }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Type"> |
||||
<span>{{ props.row.type }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Domains"> |
||||
<span>{{ props.row.custom_domains }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="SubDomain"> |
||||
<span>{{ props.row.subdomain }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Encryption"> |
||||
<span>{{ props.row.encryption }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Compression"> |
||||
<span>{{ props.row.compression }}</span> |
||||
</el-form-item> |
||||
</el-form> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Name" |
||||
prop="name" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Port" |
||||
prop="port" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Connections" |
||||
prop="conns" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Traffic In" |
||||
prop="traffic_in" |
||||
:formatter="formatTrafficIn" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Traffic Out" |
||||
prop="traffic_out" |
||||
:formatter="formatTrafficOut" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="status" |
||||
prop="status" |
||||
sortable> |
||||
<template scope="scope"> |
||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag> |
||||
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
|
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import Humanize from "humanize-plus"; |
||||
import Traffic from './Traffic.vue' |
||||
import { |
||||
HttpsProxy |
||||
} from "../utils/proxy.js" |
||||
export default { |
||||
data() { |
||||
return { |
||||
proxies: null, |
||||
vhost_https_port: "", |
||||
subdomain_host: "" |
||||
} |
||||
}, |
||||
created() { |
||||
this.fetchData() |
||||
}, |
||||
watch: { |
||||
'$route': 'fetchData' |
||||
}, |
||||
methods: { |
||||
formatTrafficIn(row, column) { |
||||
return Humanize.fileSize(row.traffic_in) |
||||
}, |
||||
formatTrafficOut(row, column) { |
||||
return Humanize.fileSize(row.traffic_out) |
||||
}, |
||||
fetchData() { |
||||
fetch('/api/serverinfo') |
||||
.then(res => { |
||||
return res.json() |
||||
}).then(json => { |
||||
this.vhost_https_port = json.vhost_https_port |
||||
this.subdomain_host = json.subdomain_host |
||||
if (this.vhost_https_port == null || this.vhost_https_port == 0) { |
||||
return |
||||
} else { |
||||
fetch('/api/proxy/https') |
||||
.then(res => { |
||||
return res.json() |
||||
}).then(json => { |
||||
this.proxies = new Array() |
||||
for (let proxyStats of json.proxies) { |
||||
this.proxies.push(new HttpsProxy(proxyStats, this.vhost_https_port, this.subdomain_host)) |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
}, |
||||
components: { |
||||
'my-traffic-chart': Traffic |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
</style> |
@ -0,0 +1,118 @@
|
||||
<template> |
||||
<div> |
||||
<el-table :data="proxies" :default-sort="{prop: 'name', order: 'ascending'}" style="width: 100%"> |
||||
<el-table-column type="expand"> |
||||
<template scope="props"> |
||||
<el-popover |
||||
ref="popover4" |
||||
placement="right" |
||||
width="600" |
||||
style="margin-left:0px" |
||||
trigger="click"> |
||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart> |
||||
</el-popover> |
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" :name="props.row.name" style="margin-bottom:10px" @click="fetchData2">Traffic Statistics</el-button> |
||||
|
||||
<el-form label-position="left" inline class="demo-table-expand"> |
||||
<el-form-item label="Name"> |
||||
<span>{{ props.row.name }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Type"> |
||||
<span>{{ props.row.type }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Addr"> |
||||
<span>{{ props.row.addr }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Encryption"> |
||||
<span>{{ props.row.encryption }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Compression"> |
||||
<span>{{ props.row.compression }}</span> |
||||
</el-form-item> |
||||
</el-form> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Name" |
||||
prop="name" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Port" |
||||
prop="port" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Connections" |
||||
prop="conns" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Traffic In" |
||||
prop="traffic_in" |
||||
:formatter="formatTrafficIn" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Traffic Out" |
||||
prop="traffic_out" |
||||
:formatter="formatTrafficOut" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="status" |
||||
prop="status" |
||||
sortable> |
||||
<template scope="scope"> |
||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag> |
||||
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import Humanize from 'humanize-plus' |
||||
import Traffic from './Traffic.vue' |
||||
import { TcpProxy } from '../utils/proxy.js' |
||||
export default { |
||||
data() { |
||||
return { |
||||
proxies: null |
||||
} |
||||
}, |
||||
created() { |
||||
this.fetchData() |
||||
}, |
||||
watch: { |
||||
'$route': 'fetchData' |
||||
}, |
||||
methods: { |
||||
formatTrafficIn(row, column) { |
||||
return Humanize.fileSize(row.traffic_in) |
||||
}, |
||||
formatTrafficOut(row, column) { |
||||
return Humanize.fileSize(row.traffic_out) |
||||
}, |
||||
fetchData() { |
||||
fetch('/api/proxy/tcp') |
||||
.then(res => { |
||||
return res.json() |
||||
}).then(json => { |
||||
this.proxies = new Array() |
||||
for (let proxyStats of json.proxies) { |
||||
this.proxies.push(new TcpProxy(proxyStats)) |
||||
} |
||||
}) |
||||
} |
||||
}, |
||||
components: { |
||||
'my-traffic-chart': Traffic |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
</style> |
@ -0,0 +1,120 @@
|
||||
<template> |
||||
<div> |
||||
<el-table :data="proxies" :default-sort="{prop: 'name', order: 'ascending'}" style="width: 100%"> |
||||
<el-table-column type="expand"> |
||||
<template scope="props"> |
||||
<el-popover |
||||
ref="popover4" |
||||
placement="right" |
||||
width="600" |
||||
style="margin-left:0px" |
||||
trigger="click"> |
||||
<my-traffic-chart :proxy_name="props.row.name"></my-traffic-chart> |
||||
</el-popover> |
||||
|
||||
<el-button v-popover:popover4 type="primary" size="small" icon="view" style="margin-bottom:10px">Traffic Statistics</el-button> |
||||
|
||||
<el-form label-position="left" inline class="demo-table-expand"> |
||||
<el-form-item label="Name"> |
||||
<span>{{ props.row.name }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Type"> |
||||
<span>{{ props.row.type }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Addr"> |
||||
<span>{{ props.row.addr }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Encryption"> |
||||
<span>{{ props.row.encryption }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="Compression"> |
||||
<span>{{ props.row.compression }}</span> |
||||
</el-form-item> |
||||
</el-form> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Name" |
||||
prop="name" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Port" |
||||
prop="port" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Connections" |
||||
prop="conns" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Traffic In" |
||||
prop="traffic_in" |
||||
:formatter="formatTrafficIn" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="Traffic Out" |
||||
prop="traffic_out" |
||||
:formatter="formatTrafficOut" |
||||
sortable> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="status" |
||||
prop="status" |
||||
sortable> |
||||
<template scope="scope"> |
||||
<el-tag type="success" v-if="scope.row.status === 'online'">{{ scope.row.status }}</el-tag> |
||||
<el-tag type="danger" v-else>{{ scope.row.status }}</el-tag> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import Humanize from "humanize-plus"; |
||||
import Traffic from './Traffic.vue' |
||||
import { |
||||
UdpProxy |
||||
} from "../utils/proxy.js" |
||||
export default { |
||||
data() { |
||||
return { |
||||
proxies: null |
||||
} |
||||
}, |
||||
created() { |
||||
this.fetchData() |
||||
}, |
||||
watch: { |
||||
'$route': 'fetchData' |
||||
}, |
||||
methods: { |
||||
formatTrafficIn(row, column) { |
||||
return Humanize.fileSize(row.traffic_in) |
||||
}, |
||||
formatTrafficOut(row, column) { |
||||
return Humanize.fileSize(row.traffic_out) |
||||
}, |
||||
fetchData() { |
||||
fetch('/api/proxy/udp') |
||||
.then(res => { |
||||
return res.json() |
||||
}).then(json => { |
||||
this.proxies = new Array() |
||||
for (let proxyStats of json.proxies) { |
||||
this.proxies.push(new UdpProxy(proxyStats)) |
||||
} |
||||
}) |
||||
} |
||||
}, |
||||
components: { |
||||
'my-traffic-chart': Traffic |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
</style> |
@ -0,0 +1,36 @@
|
||||
<template> |
||||
<div :id="proxy_name" style="width: 600px;height:400px;"></div> |
||||
</template> |
||||
|
||||
<script> |
||||
import {DrawProxyTrafficChart} from '../utils/chart.js' |
||||
export default { |
||||
props: ['proxy_name'], |
||||
created() { |
||||
this.fetchData() |
||||
}, |
||||
//watch: { |
||||
//'$route': 'fetchData' |
||||
//}, |
||||
methods: { |
||||
fetchData() { |
||||
let url = '/api/proxy/traffic/' + this.proxy_name |
||||
fetch(url) |
||||
.then(res => { |
||||
return res.json() |
||||
}).then(json => { |
||||
DrawProxyTrafficChart(this.proxy_name, json.traffic_in, json.traffic_out) |
||||
}).catch( err => { |
||||
this.$message({ |
||||
showClose: true, |
||||
message: 'Get server info from frps failed!' + err, |
||||
type: 'warning' |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
</style> |
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
|
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<title>frps dashboard</title> |
||||
</head> |
||||
|
||||
<body> |
||||
<div id="app"></div> |
||||
<!--<script src="https://code.jquery.com/jquery-3.2.0.min.js"></script>--> |
||||
<!--<script src="//cdn.bootcss.com/echarts/3.4.0/echarts.min.js"></script>--> |
||||
</body> |
||||
|
||||
</html> |
@ -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: '<App/>', |
||||
components: { App } |
||||
}) |
@ -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 |
||||
}] |
||||
}) |
@ -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 + '<br/>' |
||||
} |
||||
for (let v of data) { |
||||
let colorEl = '<span style="display:inline-block;margin-right:5px;' + |
||||
'border-radius:10px;width:9px;height:9px;background-color:' + v.color + '"></span>'; |
||||
html += colorEl + v.seriesName + ': ' + Humanize.fileSize(v.value) + '<br/>' |
||||
} |
||||
return html |
||||
} |
||||
}, |
||||
legend: { |
||||
data: ['Traffic In', 'Traffic Out'] |
||||
}, |
||||
grid: { |
||||
left: '3%', |
||||
right: '4%', |
||||
bottom: '3%', |
||||
containLabel: true |
||||
}, |
||||
xAxis: [{ |
||||
type: 'category', |
||||
data: dates |
||||
}], |
||||
yAxis: [{ |
||||
type: 'value', |
||||
axisLabel: { |
||||
formatter: function(value) { |
||||
return Humanize.fileSize(value) |
||||
} |
||||
} |
||||
}], |
||||
series: [{ |
||||
name: 'Traffic In', |
||||
type: 'bar', |
||||
data: trafficInArr |
||||
}, { |
||||
|
||||
name: 'Traffic Out', |
||||
type: 'bar', |
||||
data: trafficOutArr |
||||
}] |
||||
}; |
||||
myChart.setOption(option); |
||||
myChart.hideLoading() |
||||
} |
||||
|
||||
export { |
||||
DrawTrafficChart, |
||||
DrawProxyChart, |
||||
DrawProxyTrafficChart |
||||
} |
@ -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,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} |
@ -0,0 +1,2 @@
|
||||
import Vue from 'vue' |
||||
import ElementUI from 'element-ui' |
@ -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'
|
||||
}) |
Loading…
Reference in new issue