mirror of
https://github.com/fatedier/frp.git
synced 2025-07-28 08:27:41 +00:00
add admin UI for frpc
This commit is contained in:
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 |
106
web/frpc/src/components/Configure.vue
Normal file
106
web/frpc/src/components/Configure.vue
Normal file
@@ -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>
|
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}
|
Reference in New Issue
Block a user