更改目录结构

This commit is contained in:
bjdgyc
2021-03-01 15:46:08 +08:00
parent 3464d1d10e
commit 0f91c779e3
105 changed files with 29099 additions and 96 deletions

84
web/src/App.vue Normal file
View File

@@ -0,0 +1,84 @@
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app',
components: {},
created() {
const token = sessionStorage.getItem('jwtToken');
console.log("App created", token)
if (token) {
this.$root.isLogin = true
}
},
mounted() {
console.log("App mounted")
},
data() {
return {}
},
computed: {
currentComponent: function () {
var isLogin = this.$root.isLogin
console.log("App isLogin", isLogin)
if (isLogin) {
return "layout";
}
return "login";
},
},
}
</script>
<style>
html, body {
height: 100%;
margin: 0;
}
#app {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/*color: #2c3e50;*/
/*border: 1px solid red;*/
height: 100%;
/*width:100%;*/
/*box-sizing: border-box;*/
/*padding: 4px;*/
}
.hide {
display: none;
}
/*space vertical*/
.sh-10 {
height: 10px;
}
.sh-20 {
height: 20px;
}
/*space horizontal*/
.sw-10 {
height: 1px;
width: 10px;
}
.sw-20 {
height: 1px;
width: 20px;
}
.m-left-10 {
margin-left: 10px;
}
</style>

BIN
web/src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,37 @@
<template>
<div>
<div class="monitor">
<div class="monitor-left">{{ left }}</div>
<div class="monitor-right">{{ right }}</div>
</div>
<el-divider v-if="divider"></el-divider>
</div>
</template>
<script>
export default {
name: "Cell",
props: {
left: {},
right: {},
divider: {type: Boolean}
},
}
</script>
<style scoped>
.monitor {
display: flex;
justify-content: space-between;
align-items: center;
}
.monitor-left {
font-size: 14px;
}
.monitor-right {
font-size: 12px;
color: #909399;
}
</style>

View File

@@ -0,0 +1,82 @@
<template>
<div id="line-chart" :style="{height:height,width:width}"/>
</template>
<script>
import echarts from 'echarts'
export default {
name: 'LineChart',
props: {
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '350px'
},
// title,xAxis,series
chartData: {
type: Object,
required: true
}
},
data() {
return {}
},
mounted() {
this.initChart()
},
beforeDestroy() {
},
methods: {
initChart() {
let chart = echarts.init(this.$el)
const option = {
title: {
text: this.chartData.title || '折线图'
},
tooltip: {
trigger: 'axis'
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
// toolbox: {
// feature: {
// saveAsImage: {}
// }
// },
xAxis: {
type: 'category',
boundaryGap: false,
data: this.chartData.xname
},
yAxis: {
type: 'value'
},
series: [],
};
let xdata = this.chartData.xdata
for (let key in xdata) {
// window.console.log(key);
let a = {
name: key,
type: 'line',
data: xdata[key]
};
option.series.push(a)
}
chart.setOption(option)
},
}
}
</script>

63
web/src/layout/Layout.vue Normal file
View File

@@ -0,0 +1,63 @@
<template>
<el-container style="height: 100%;">
<!--侧边栏菜单-->
<el-aside :width="is_active?'200':'64'">
<LayoutAside :is_active="is_active" :route_path="route_path"/>
</el-aside>
<el-container>
<!--正文头部内容-->
<el-header>
<!--监听子组件的变量事件-->
<LayoutHeader :is_active.sync="is_active" :route_name="route_name"/>
</el-header>
<!--正文内容-->
<!--style="background-color: rgb(240, 242, 245);"-->
<el-main style="background-color: #fbfbfb">
<!-- 对应的组件内容渲染到router-view中 -->
<!--子组件上报route信息-->
<router-view :route_path.sync="route_path" :route_name.sync="route_name"></router-view>
</el-main>
</el-container>
</el-container>
</template>
<script>
import LayoutAside from "@/layout/LayoutAside";
import LayoutHeader from "@/layout/LayoutHeader";
export default {
name: "Layout",
components: {LayoutHeader, LayoutAside},
data() {
return {
is_active: true,
route_path: '/index',
route_name: ['首页'],
}
},
watch: {
route_path: function (val) {
// var w = document.getElementById('layout-menu').clientWidth;
window.console.log('is_active', val)
},
},
created() {
window.console.log('layout-route', this.$route)
},
}
</script>
<style>
.el-header {
background-color: #fff;
/*box-shadow: 0 1px 4px rgba(0, 21, 41, .08);*/
color: #333;
line-height: 60px;
/*width: 100%;*/
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<!--background-color="#304156"-->
<!--text-color="#bfcbd9"-->
<!--active-text-color="#409EFF"-->
<!--:unique-opened="false"-->
<!--<div class="layout-aside" :style="aside_style">-->
<el-menu :collapse="!is_active"
:default-active="route_path"
:style="is_active?'width:200px':''"
router
class="layout-menu"
:collapse-transition="false"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-menu-item index="/admin/home">
<i class="el-icon-s-home"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-menu"></i>
<span slot="title">基础信息</span>
</template>
<el-menu-item index="/admin/set/system">系统信息</el-menu-item>
<el-menu-item index="/admin/set/soft">软件配置</el-menu-item>
<el-menu-item index="/admin/set/other">其他设置</el-menu-item>
</el-submenu>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-s-custom"></i>
<span slot="title">用户信息</span>
</template>
<el-menu-item index="/admin/user/list">用户列表</el-menu-item>
<el-menu-item index="/admin/user/online">在线用户</el-menu-item>
<el-menu-item index="/admin/user/ip_map">IP映射</el-menu-item>
</el-submenu>
<el-submenu index="3">
<template slot="title">
<i class="el-icon-s-order"></i>
<span slot="title">用户组信息</span>
</template>
<el-menu-item index="/admin/group/list">用户组列表</el-menu-item>
</el-submenu>
</el-menu>
</template>
<script>
export default {
name: "LayoutAside",
data() {
return {}
},
props: ['is_active', 'route_path'],
mounted() {
}
}
</script>
<style scoped>
.layout-menu {
height: 100%;
}
</style>

View File

@@ -0,0 +1,82 @@
<template>
<div class="layout-header">
<div>
<i @click="toggleClick" :class="is_active ? 'el-icon-s-fold' : 'el-icon-s-unfold'" class="toggle-icon"
style="font-size: 26px;"></i>
<el-breadcrumb separator="/" class="app-breadcrumb">
<el-breadcrumb-item v-for="(item, index) in route_name" :key="index">{{ item }}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<el-dropdown trigger="click" @command="handleCommand">
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="logout">退出</el-dropdown-item>
</el-dropdown-menu>
<span style="font-size: 12px;">{{ admin_user }}</span>
</el-dropdown>
</div>
</template>
<script>
import {getUser, removeToken} from "@/plugins/token";
export default {
name: "Layoutheader",
props: ['route_name'],
data() {
return {
is_active: true
}
},
computed: {
admin_user() {
return getUser();
},
},
methods: {
// 菜单栏开关按钮
toggleClick() {
this.is_active = !this.is_active
// 触发事件,抛出到上层
this.$emit('update:is_active', this.is_active)
},
handleCommand() {
console.log("handleCommand")
// 退出 删除登录信息
removeToken()
this.$router.push("/login");
},
}
}
</script>
<style scoped>
.layout-header {
display: flex;
justify-content: space-between;
align-items: center
}
.toggle-icon {
cursor: pointer;
transition: background .3s;
-webkit-tap-highlight-color: transparent;
}
.toggle-icon:hover {
background: rgba(0, 0, 0, .025)
}
.app-breadcrumb {
display: inline-block;
font-size: 14px;
/*line-height: 20;*/
margin-left: 20px;
}
</style>

22
web/src/main.js Normal file
View File

@@ -0,0 +1,22 @@
import Vue from 'vue'
import App from './App.vue'
import './plugins/element'
import "./plugins/mixin";
import request from './plugins/request'
import router from "./plugins/router";
//TODO
Vue.config.productionTip = false
const vm = new Vue({
data: {
// 判断是否登录
isLogin: false,
},
router,
render: h => h(App),
}).$mount('#app')
request(vm)

160
web/src/pages/Home.vue Normal file
View File

@@ -0,0 +1,160 @@
<template>
<div class="home">
<el-row :gutter="40" class="panel-group">
<el-col :span="6" class="card-panel-col">
<div class="card-panel">
<i class="el-icon-user-solid" style="font-size:50px;color: #f4516c;"></i>
<div class="card-panel-description">
<div class="card-panel-text">在线数</div>
<countTo :startVal='0' :endVal='counts.online' :duration='2000' class="panel-num"></countTo>
</div>
</div>
</el-col>
<el-col :span="6" class="card-panel-col">
<div class="card-panel">
<i class="el-icon-user-solid" style="font-size:50px;color: #36a3f7"></i>
<div class="card-panel-description">
<div class="card-panel-text">用户数</div>
<countTo :startVal='0' :endVal='counts.user' :duration='2000' class="panel-num"></countTo>
</div>
</div>
</el-col>
<el-col :span="6" class="card-panel-col">
<div class="card-panel">
<i class="el-icon-wallet" style="font-size:50px;color:#34bfa3"></i>
<div class="card-panel-description">
<div class="card-panel-text">用户组数</div>
<countTo :startVal='0' :endVal='counts.group' :duration='2000' class="panel-num"></countTo>
</div>
</div>
</el-col>
<el-col :span="6" class="card-panel-col">
<div class="card-panel">
<i class="el-icon-s-order" style="font-size:50px;color:#40c9c6"></i>
<div class="card-panel-description">
<div class="card-panel-text">IP映射数</div>
<countTo :startVal='0' :endVal='counts.ip_map' :duration='2000' class="panel-num"></countTo>
</div>
</div>
</el-col>
</el-row>
<el-row class="line-chart">
<LineChart :chart-data="lineChartUser"/>
</el-row>
<el-row class="line-chart">
<LineChart :chart-data="lineChartOrder"/>
</el-row>
</div>
</template>
<script>
import countTo from 'vue-count-to';
import LineChart from "@/components/LineChart";
import axios from "axios";
const lineChartUser = {
title: '每日在线统计',
xname: ['2019-12-13', '2019-12-14', '2019-12-15', '2019-12-16', '2019-12-17', '2019-12-18', '2019-12-19'],
xdata: {
'test1': [10, 120, 11, 134, 105, 10, 15],
'test2': [10, 82, 91, 14, 162, 10, 15]
}
}
const lineChartOrder = {
title: '每日流量统计',
xname: ['2019-12-13', '2019-12-14', '2019-12-15', '2019-12-16', '2019-12-17', '2019-12-18', '2019-12-19'],
xdata: {
'test1': [100, 120, 161, 134, 105, 160, 165],
'test2': [120, 82, 91, 154, 162, 140, 145]
}
}
export default {
name: "Home",
components: {
LineChart,
countTo,
},
data() {
return {
counts: {
online: 0,
user: 0,
group: 0,
ip_map: 0,
},
lineChartUser: lineChartUser,
lineChartOrder: lineChartOrder,
}
},
created() {
this.$emit('update:route_path', this.$route.path)
this.$emit('update:route_name', ['首页'])
},
mounted() {
this.getData()
},
methods: {
getData() {
axios.get('/set/home').then(resp => {
var data = resp.data.data
console.log(data);
this.counts = data.counts
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
},
}
</script>
<style scoped>
.card-panel {
display: flex;
justify-content: space-around;
border: 1px solid red;
padding: 30px 0;
color: #666;
background: #fff;
/*box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);*/
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
border-color: rgba(0, 0, 0, .05);
}
.card-panel-description {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
.card-panel-text {
line-height: 18px;
color: rgba(0, 0, 0, .45);
font-size: 16px;
}
.panel-num {
font-size: 20px;
font-weight: 700;
}
.line-chart {
background: #fff;
padding: 0 16px;
margin-top: 40px;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
border-color: rgba(0, 0, 0, .05);
}
</style>

118
web/src/pages/Login.vue Normal file
View File

@@ -0,0 +1,118 @@
<template>
<div class="login">
<el-card style="width: 550px;">
<div class="issuer">AnyLink SSL VPN管理后台</div>
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm">
<el-form-item label="管理用户名" prop="admin_user">
<el-input v-model="ruleForm.admin_user"></el-input>
</el-form-item>
<el-form-item label="管理密码" prop="admin_pass">
<el-input type="password" v-model="ruleForm.admin_pass" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="isLoading" @click="submitForm('ruleForm')">登录</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script>
import axios from "axios";
import qs from "qs";
import {setToken, setUser} from "@/plugins/token";
export default {
name: "Login",
mounted() {
// 进入login删除登录信息
console.log("login created")
//绑定事件
window.addEventListener('keydown', this.keyDown);
},
destroyed(){
window.removeEventListener('keydown',this.keyDown,false);
},
data() {
return {
ruleForm: {},
rules: {
admin_user: [
{required: true, message: '请输入用户名', trigger: 'blur'},
{max: 50, message: '长度小于 50 个字符', trigger: 'blur'}
],
admin_pass: [
{required: true, message: '请输入密码', trigger: 'blur'},
{min: 6, message: '长度大于 6 个字符', trigger: 'blur'}
],
},
}
},
methods: {
keyDown(e) {
//如果是回车则执行登录方法
if (e.keyCode === 13) {
this.submitForm('ruleForm');
}
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (!valid) {
console.log('error submit!!');
return false;
}
this.isLoading = true
// alert('submit!');
axios.post('/base/login', qs.stringify(this.ruleForm)).then(resp => {
var rdata = resp.data
if (rdata.code === 0) {
this.$message.success(rdata.msg);
setToken(rdata.data.token)
setUser(rdata.data.admin_user)
this.$router.push("/home");
} else {
this.$message.error(rdata.msg);
}
console.log(rdata);
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
}).finally(() => {
this.isLoading = false
}
);
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
},
},
}
</script>
<style scoped>
.login {
/*border: 1px solid red;*/
height: 100%;
/*margin: 0 auto;*/
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
.issuer {
font-size: 26px;
font-weight: bold;
margin-bottom: 50px;
}
</style>

View File

@@ -0,0 +1,447 @@
<template>
<div>
<el-card>
<el-form :inline="true">
<el-form-item>
<el-button
size="small"
type="primary"
icon="el-icon-plus"
@click="handleEdit('')">添加
</el-button>
</el-form-item>
</el-form>
<el-table
ref="multipleTable"
:data="tableData"
border>
<el-table-column
sortable="true"
prop="id"
label="ID"
width="60">
</el-table-column>
<el-table-column
prop="name"
label="组名">
</el-table-column>
<el-table-column
prop="note"
label="备注">
</el-table-column>
<el-table-column
prop="allow_lan"
label="本地网络">
<template slot-scope="scope">
<el-switch
v-model="scope.row.allow_lan"
disabled>
</el-switch>
</template>
</el-table-column>
<el-table-column
prop="bandwidth"
label="带宽限制">
</el-table-column>
<el-table-column
prop="client_dns"
label="客户端DNS"
width="160">
<template slot-scope="scope">
<el-row v-for="(item,inx) in scope.row.client_dns" :key="inx">{{ item.val }}</el-row>
</template>
</el-table-column>
<el-table-column
prop="route_include"
label="路由包含"
width="200">
<template slot-scope="scope">
<el-row v-for="(item,inx) in scope.row.route_include" :key="inx">{{ item.val }}</el-row>
</template>
</el-table-column>
<el-table-column
prop="route_exclude"
label="路由排除"
width="200">
<template slot-scope="scope">
<el-row v-for="(item,inx) in scope.row.route_exclude" :key="inx">{{ item.val }}</el-row>
</template>
</el-table-column>
<el-table-column
prop="link_acl"
label="LINK-ACL"
min-width="200">
<template slot-scope="scope">
<el-row v-for="(item,inx) in scope.row.link_acl" :key="inx">
{{ item.action }} => {{ item.val }} : {{ item.port }}
</el-row>
</template>
</el-table-column>
<el-table-column
prop="status"
label="状态"
width="70">
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 1" type="success">可用</el-tag>
<el-tag v-else type="danger">停用</el-tag>
</template>
</el-table-column>
<el-table-column
prop="updated_at"
label="更新时间"
:formatter="tableDateFormat">
</el-table-column>
<el-table-column
label="操作"
width="150">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
@click="handleEdit(scope.row)">编辑
</el-button>
<el-popconfirm
style="margin-left: 10px"
@onConfirm="handleDel(scope.row)"
title="确定要删除用户吗?">
<el-button
slot="reference"
size="mini"
type="danger">删除
</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div class="sh-20"></div>
<el-pagination
background
layout="prev, pager, next"
:pager-count="11"
@current-change="pageChange"
:current-page="page"
:total="count">
</el-pagination>
</el-card>
<!--新增修改弹出框-->
<el-dialog
:close-on-click-modal="false"
title="用户组"
:visible.sync="user_edit_dialog"
width="750px"
center>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm">
<el-form-item label="用户组ID" prop="id">
<el-input v-model="ruleForm.id" disabled></el-input>
</el-form-item>
<el-form-item label="组名" prop="name">
<el-input v-model="ruleForm.name" :disabled="ruleForm.id > 0"></el-input>
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input v-model="ruleForm.note"></el-input>
</el-form-item>
<el-form-item label="带宽限制" prop="bandwidth">
<el-input v-model.number="ruleForm.bandwidth">
<template slot="append">BYTE</template>
</el-input>
</el-form-item>
<el-form-item label="本地网络" prop="allow_lan">
<el-switch
v-model="ruleForm.allow_lan">
</el-switch>
</el-form-item>
<el-form-item label="客户端DNS" prop="client_dns">
<el-row class="msg-info">
<el-col :span="20">输入IP格式如: 192.168.0.10</el-col>
<el-col :span="4">
<el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.client_dns)"></el-button>
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.client_dns)"></el-button>
</el-col>
</el-row>
<el-row v-for="(item,index) in ruleForm.client_dns"
:key="index" style="margin-bottom: 5px" gutter="10">
<el-col :span="10">
<el-input v-model="item.val"></el-input>
</el-col>
<el-col :span="14">
<el-input v-model="item.note" placeholder="备注"></el-input>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="包含路由" prop="route_include">
<el-row class="msg-info">
<el-col :span="20">输入CIDR格式如: 192.168.1.0/24</el-col>
<el-col :span="4">
<el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.route_include)"></el-button>
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.route_include)"></el-button>
</el-col>
</el-row>
<el-row v-for="(item,index) in ruleForm.route_include"
:key="index" style="margin-bottom: 5px" gutter="10">
<el-col :span="10">
<el-input v-model="item.val"></el-input>
</el-col>
<el-col :span="14">
<el-input v-model="item.note" placeholder="备注"></el-input>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="排除路由" prop="route_exclude">
<el-row class="msg-info">
<el-col :span="20">输入CIDR格式如: 192.168.2.0/24</el-col>
<el-col :span="4">
<el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.route_exclude)"></el-button>
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.route_exclude)"></el-button>
</el-col>
</el-row>
<el-row v-for="(item,index) in ruleForm.route_exclude"
:key="index" style="margin-bottom: 5px" gutter="10">
<el-col :span="10">
<el-input v-model="item.val"></el-input>
</el-col>
<el-col :span="14">
<el-input v-model="item.note" placeholder="备注"></el-input>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="权限控制" prop="link_acl">
<el-row class="msg-info">
<el-col :span="20">输入CIDR格式如: 192.168.3.0/24 端口0表示所有端口</el-col>
<el-col :span="4">
<el-button size="mini" type="success" icon="el-icon-plus" circle
@click.prevent="addDomain(ruleForm.link_acl)"></el-button>
<el-button size="mini" type="danger" icon="el-icon-minus" circle
@click.prevent="removeDomain(ruleForm.link_acl)"></el-button>
</el-col>
</el-row>
<el-row v-for="(item,index) in ruleForm.link_acl"
:key="index" style="margin-bottom: 5px" gutter="5">
<el-col :span="11">
<el-input placeholder="请输入CIDR地址" v-model="item.val">
<el-select v-model="item.action" slot="prepend">
<el-option label="允许" value="allow"></el-option>
<el-option label="禁止" value="deny"></el-option>
</el-select>
</el-input>
</el-col>
<el-col :span="3">
<el-input v-model.number="item.port" placeholder="端口"></el-input>
</el-col>
<el-col :span="10">
<el-input v-model="item.note" placeholder="备注"></el-input>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="ruleForm.status">
<el-radio :label="1" border>启用</el-radio>
<el-radio :label="0" border>停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">保存</el-button>
<el-button @click="disVisible">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "List",
components: {},
mixins: [],
created() {
this.$emit('update:route_path', this.$route.path)
this.$emit('update:route_name', ['用户组信息', '用户组列表'])
},
mounted() {
this.getData(1)
},
data() {
return {
page: 1,
tableData: [],
count: 10,
ruleForm: {
bandwidth: 0,
status: 1,
allow_lan: true,
client_dns: [{val: '114.114.114.114'}],
route_include: [],
route_exclude: [],
link_acl: [],
},
rules: {
name: [
{required: true, message: '请输入用户名', trigger: 'blur'},
{max: 30, message: '长度小于 30 个字符', trigger: 'blur'}
],
bandwidth: [
{required: true, message: '请输入用户姓名', trigger: 'blur'},
{type: 'number', message: '年龄必须为数字值'}
],
email: [
{required: true, message: '请输入用户邮箱', trigger: 'blur'},
{type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']}
],
status: [
{required: true}
],
},
}
},
methods: {
handleDel(row) {
axios.post('/group/del?id=' + row.id).then(resp => {
const rdata = resp.data;
if (rdata.code === 0) {
this.$message.success(rdata.msg);
this.getData(1);
} else {
this.$message.error(rdata.msg);
}
console.log(rdata);
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
handleEdit(row) {
!this.$refs['ruleForm'] || this.$refs['ruleForm'].resetFields();
console.log(row)
this.user_edit_dialog = true
if (!row) {
return;
}
axios.get('/group/detail', {
params: {
id: row.id,
}
}).then(resp => {
this.ruleForm = resp.data.data
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
pageChange(p) {
this.getData(p)
},
getData(page) {
this.page = page
axios.get('/group/list', {
params: {
page: page,
}
}).then(resp => {
const rdata = resp.data.data;
console.log(rdata);
this.tableData = rdata.datas;
this.count = rdata.count
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
removeDomain(arr, item) {
console.log(item)
// let index = arr.indexOf(item);
// if (index !== -1 && arr.length > 1) {
// arr.splice(index, 1)
// }
arr.pop()
},
addDomain(arr) {
arr.push({val: "", action: "allow", port: 0});
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (!valid) {
console.log('error submit!!');
return false;
}
axios.post('/group/set', this.ruleForm).then(resp => {
const rdata = resp.data;
if (rdata.code === 0) {
this.$message.success(rdata.msg);
this.getData(1);
} else {
this.$message.error(rdata.msg);
}
console.log(rdata);
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
},
}
</script>
<style scoped>
.msg-info {
background-color: #f4f4f5;
color: #909399;
padding: 0 5px;
margin: 0;
box-sizing: border-box;
border-radius: 4px;
font-size: 12px;
}
.el-select {
width: 80px;
}
</style>

183
web/src/pages/set/Other.vue Normal file
View File

@@ -0,0 +1,183 @@
<template>
<el-card>
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="邮件配置" name="dataSmtp">
<el-form :model="dataSmtp" ref="dataSmtp" :rules="rules" label-width="100px" class="tab-one">
<el-form-item label="服务器地址" prop="host">
<el-input v-model="dataSmtp.host"></el-input>
</el-form-item>
<el-form-item label="服务器端口" prop="port">
<el-input v-model.number="dataSmtp.port"></el-input>
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="dataSmtp.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="dataSmtp.password"></el-input>
</el-form-item>
<el-form-item label="启用SSL" prop="use_ssl">
<el-switch v-model="dataSmtp.use_ssl"></el-switch>
</el-form-item>
<el-form-item label="邮件from" prop="from">
<el-input v-model="dataSmtp.from"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('dataSmtp')">保存</el-button>
<el-button @click="resetForm('dataSmtp')">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="其他设置" name="dataOther">
<el-form :model="dataOther" ref="dataOther" :rules="rules" label-width="100px" class="tab-one">
<el-form-item label="Banner信息" prop="banner">
<el-input
type="textarea"
:rows="5"
placeholder="请输入内容"
v-model="dataOther.banner">
</el-input>
</el-form-item>
<el-form-item label="账户开通邮件" prop="account_mail">
<el-input
type="textarea"
:rows="10"
placeholder="请输入内容"
v-model="dataOther.account_mail">
</el-input>
</el-form-item>
<el-form-item label="邮件展示">
<iframe
width="500px"
height="300px"
:srcdoc="dataOther.account_mail">
</iframe>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('dataOther')">保存</el-button>
<el-button @click="resetForm('dataOther')">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-card>
</template>
<script>
import axios from "axios";
export default {
name: "Other",
created() {
this.$emit('update:route_path', this.$route.path)
this.$emit('update:route_name', ['基础信息', '其他设置'])
},
mounted() {
this.getSmtp()
},
data() {
return {
activeName: 'dataSmtp',
dataSmtp: {},
dataOther: {},
rules: {
host: {required: true, message: '请输入服务器地址', trigger: 'blur'},
port: [
{required: true, message: '请输入服务器端口', trigger: 'blur'},
{type: 'number', message: '请输入正确的服务器端口', trigger: ['blur', 'change']}
],
issuer: {required: true, message: '请输入系统名称', trigger: 'blur'},
},
};
},
methods: {
handleClick(tab, event) {
window.console.log(tab.name, event);
switch (tab.name) {
case "dataSmtp":
this.getSmtp()
break
case "dataOther":
this.getOther()
break
}
},
getSmtp() {
axios.get('/set/other/smtp').then(resp => {
let rdata = resp.data
console.log(rdata)
if (rdata.code !== 0) {
this.$message.error(rdata.msg);
return;
}
this.dataSmtp = rdata.data
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
getOther() {
axios.get('/set/other').then(resp => {
let rdata = resp.data
console.log(rdata)
if (rdata.code !== 0) {
this.$message.error(rdata.msg);
return;
}
this.dataOther = rdata.data
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (!valid) {
alert('error submit!');
}
switch (formName) {
case "dataSmtp":
axios.post('/set/other/smtp/edit', this.dataSmtp).then(resp => {
var rdata = resp.data
console.log(rdata);
if (rdata.code === 0) {
this.$message.success(rdata.msg);
} else {
this.$message.error(rdata.msg);
}
})
break;
case "dataOther":
axios.post('/set/other/edit', this.dataOther).then(resp => {
var rdata = resp.data
console.log(rdata);
if (rdata.code === 0) {
this.$message.success(rdata.msg);
} else {
this.$message.error(rdata.msg);
}
})
break;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
},
}
</script>
<style scoped>
.tab-one {
width: 600px;
}
</style>

View File

@@ -0,0 +1,64 @@
<template>
<el-card>
<el-table
:data="soft_data"
border>
<el-table-column
prop="info"
label="信息"
width="260">
</el-table-column>
<el-table-column
prop="name"
label="配置"
width="200">
</el-table-column>
<el-table-column
prop="data"
label="数据">
<template slot-scope="scope">
{{ scope.row.data }}
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script>
import axios from "axios";
export default {
name: "Soft",
created() {
this.$emit('update:route_path', this.$route.path)
this.$emit('update:route_name', ['基础信息', '软件配置'])
},
mounted() {
this.getSoftInfo()
},
data() {
return {soft_data: []}
},
methods: {
getSoftInfo() {
axios.get('/set/soft', {}).then(resp => {
var data = resp.data
console.log(data);
this.soft_data = data.data;
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
}
},
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,133 @@
<template>
<div>
<el-row :gutter="10" class="mb10">
<el-col :span="8">
<el-card v-if="system.cpu" body-style="text-align: center;">
<div slot="header">
<span>CPU使用率</span>
</div>
<el-progress type="circle" :percentage="system.cpu.percent" style="margin-bottom: 20px"/>
<Cell left="CPU主频" :right="system.cpu.ghz" divider/>
<Cell left="系统负载" :right="system.sys.load"/>
</el-card>
</el-col>
<el-col :span="8">
<el-card v-if="system.mem" body-style="text-align: center;">
<div slot="header">
<span>内存使用率</span>
</div>
<el-progress type="circle" :percentage="system.mem.percent" style="margin-bottom: 20px"/>
<Cell left="总内存" :right="system.mem.total" divider/>
<Cell left="剩余内存" :right="system.mem.free"/>
</el-card>
</el-col>
<el-col :span="8">
<el-card v-if="system.disk" body-style="text-align: center;">
<div slot="header">
<span>磁盘信息</span>
</div>
<el-progress type="circle" :percentage="system.disk.percent" style="margin-bottom: 20px"/>
<Cell left="总存储" :right="system.disk.total" divider/>
<Cell left="剩余存储" :right="system.disk.free"/>
</el-card>
</el-col>
</el-row>
<el-card v-if="system.sys" style="margin-top: 10px">
<div slot="header">
<span>go运行环境</span>
</div>
<Cell left="GO版本" :right="system.sys.goOs" divider/>
<Cell left="GoArch" :right="system.sys.goArch" divider/>
<Cell left="GoVersion" :right="system.sys.goVersion" divider/>
<Cell left="Goroutine" :right="system.sys.goroutine"/>
</el-card>
<el-card v-if="system.sys" style="margin-top: 10px">
<div slot="header">
<span>服务器信息</span>
</div>
<Cell left="机器名称" :right="system.sys.hostname" divider/>
<Cell left="操作系统" :right="system.sys.platform" divider/>
<Cell left="内核版本" :right="system.sys.kernel" divider/>
<Cell left="CPU核心" :right="system.cpu.core" divider/>
<Cell left="CPU" :right="system.cpu.modelName"/>
</el-card>
</div>
</template>
<script>
import Cell from "@/components/Cell";
import axios from "axios";
export default {
name: 'Monitor',
components: {Cell},
created() {
this.$emit('update:route_path', this.$route.path)
this.$emit('update:route_name', ['基础信息', '系统信息'])
},
mounted() {
this.getData();
// const chatTimer = setInterval(() => {
// this.getData();
// }, 2000);
//
// this.$once('hook:beforeDestroy', () => {
// clearInterval(chatTimer);
// });
},
data() {
return {system: {}}
},
methods: {
getData() {
axios.get('/set/system', {}).then(resp => {
var data = resp.data.data
console.log(data);
this.system = data;
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
}
}
}
</script>
<style scoped>
.monitor {
display: flex;
justify-content: space-between;
align-items: center;
}
.monitor-left {
font-size: 14px;
}
.monitor-right {
font-size: 12px;
color: #909399;
}
</style>

View File

@@ -0,0 +1,273 @@
<template>
<div>
<el-card>
<el-form :inline="true">
<el-form-item>
<el-button
size="small"
type="primary"
icon="el-icon-plus"
@click="handleEdit('')">添加
</el-button>
</el-form-item>
</el-form>
<el-table
ref="multipleTable"
:data="tableData"
border>
<el-table-column
sortable="true"
prop="id"
label="ID"
width="60">
</el-table-column>
<el-table-column
prop="ip_addr"
label="IP地址">
</el-table-column>
<el-table-column
prop="mac_addr"
label="MAC地址">
</el-table-column>
<el-table-column
prop="username"
label="用户名">
</el-table-column>
<el-table-column
prop="keep"
label="IP保留">
<template slot-scope="scope">
<!-- <el-tag v-if="scope.row.keep" type="success">保留</el-tag>-->
<el-switch
disabled
v-model="scope.row.keep"
active-color="#13ce66">
</el-switch>
</template>
</el-table-column>
<el-table-column
prop="note"
label="备注">
</el-table-column>
<el-table-column
prop="last_login"
label="最后登陆时间"
:formatter="tableDateFormat">
</el-table-column>
<el-table-column
label="操作"
width="150">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
@click="handleEdit(scope.row)">编辑
</el-button>
<el-popconfirm
class="m-left-10"
@onConfirm="handleDel(scope.row)"
title="确定要删除IP映射吗">
<el-button
slot="reference"
size="mini"
type="danger"
@click="handleDelete(scope.row)">删除
</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div class="sh-20"></div>
<el-pagination
background
layout="prev, pager, next"
:pager-count="11"
@current-change="pageChange"
:total="count">
</el-pagination>
</el-card>
<!--新增修改弹出框-->
<el-dialog
title="提示"
:close-on-click-modal="false"
:visible="user_edit_dialog"
@close="disVisible"
width="600px"
center>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm">
<el-form-item label="ID" prop="id">
<el-input v-model="ruleForm.id" disabled></el-input>
</el-form-item>
<el-form-item label="IP地址" prop="ip_addr">
<el-input v-model="ruleForm.ip_addr"></el-input>
</el-form-item>
<el-form-item label="MAC地址" prop="mac_addr">
<el-input v-model="ruleForm.mac_addr"></el-input>
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="ruleForm.username"></el-input>
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input v-model="ruleForm.note"></el-input>
</el-form-item>
<el-form-item label="IP保留" prop="keep">
<el-switch
v-model="ruleForm.keep"
active-color="#13ce66">
</el-switch>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">保存</el-button>
<el-button @click="disVisible">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "IpMap",
components: {},
mixins:[],
created() {
this.$emit('update:route_path', this.$route.path)
this.$emit('update:route_name', ['用户信息', 'IP映射'])
},
mounted() {
this.getData(1)
},
data() {
return {
tableData: [],
count: 10,
nowIndex: 0,
ruleForm: {
status: 1,
groups: [],
},
rules: {
username: [
{required: false, message: '请输入用户名', trigger: 'blur'},
{max: 50, message: '长度小于 50 个字符', trigger: 'blur'}
],
mac_addr: [
{required: true, message: '请输入mac地址', trigger: 'blur'}
],
ip_addr: [
{required: true, message: '请输入ip地址', trigger: 'blur'}
],
status: [
{required: true}
],
},
}
},
methods: {
getData(p) {
axios.get('/user/ip_map/list', {
params: {
page: p,
}
}).then(resp => {
var data = resp.data.data
console.log(data);
this.tableData = data.datas;
this.count = data.count
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
pageChange(p) {
this.getData(p)
},
handleEdit(row) {
!this.$refs['ruleForm'] || this.$refs['ruleForm'].resetFields();
console.log(row)
this.user_edit_dialog = true
if (!row) {
return;
}
axios.get('/user/ip_map/detail', {
params: {
id: row.id,
}
}).then(resp => {
this.ruleForm = resp.data.data
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
handleDel(row) {
axios.post('/user/ip_map/del?id=' + row.id).then(resp => {
var rdata = resp.data
if (rdata.code === 0) {
this.$message.success(rdata.msg);
this.getData(1);
} else {
this.$message.error(rdata.msg);
}
console.log(rdata);
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (!valid) {
console.log('error submit!!');
return false;
}
// alert('submit!');
axios.post('/user/ip_map/set', this.ruleForm).then(resp => {
var rdata = resp.data
if (rdata.code === 0) {
this.$message.success(rdata.msg);
this.getData(1);
} else {
this.$message.error(rdata.msg);
}
console.log(rdata);
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
});
},
},
}
</script>
<style scoped>
</style>

410
web/src/pages/user/List.vue Normal file
View File

@@ -0,0 +1,410 @@
<template>
<div>
<el-card>
<el-form :inline="true">
<el-form-item>
<el-button
size="small"
type="primary"
icon="el-icon-plus"
@click="handleEdit('')">添加
</el-button>
</el-form-item>
<el-form-item label="用户名:">
<el-input size="small" v-model="searchData" placeholder="请输入内容"></el-input>
</el-form-item>
<el-form-item>
<el-button
size="small"
type="primary"
icon="el-icon-search"
@click="handleSearch()">搜索
</el-button>
<el-button
size="small"
icon="el-icon-refresh"
@click="searchData=''">重置搜索
</el-button>
</el-form-item>
</el-form>
<el-table
ref="multipleTable"
:data="tableData"
border>
<el-table-column
sortable="true"
prop="id"
label="ID"
width="60">
</el-table-column>
<el-table-column
prop="username"
label="用户名"
width="150">
</el-table-column>
<el-table-column
prop="nickname"
label="姓名"
width="100">
</el-table-column>
<el-table-column
prop="email"
label="邮箱">
</el-table-column>
<el-table-column
prop="otp_secret"
label="OTP密钥"
width="110">
<template slot-scope="scope">
<el-button
v-if="!scope.row.disable_otp"
type="text"
icon="el-icon-view"
@click="getOtpImg(scope.row)">
{{ scope.row.otp_secret.substring(0, 6) }}
</el-button>
</template>
</el-table-column>
<el-table-column
prop="groups"
label="用户组">
<template slot-scope="scope">
<el-row v-for="item in scope.row.groups" :key="item">{{ item }}</el-row>
</template>
</el-table-column>
<el-table-column
prop="status"
label="状态"
width="70">
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 1" type="success">可用</el-tag>
<el-tag v-else type="danger">停用</el-tag>
</template>
</el-table-column>
<el-table-column
prop="updated_at"
label="更新时间"
:formatter="tableDateFormat">
</el-table-column>
<el-table-column
label="操作"
width="210">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
@click="handleEdit(scope.row)">编辑
</el-button>
<!-- <el-popconfirm
class="m-left-10"
@onConfirm="handleClick('reset',scope.row)"
title="确定要重置用户密码和密钥吗?">
<el-button
slot="reference"
size="mini"
type="warning">重置
</el-button>
</el-popconfirm>-->
<el-popconfirm
class="m-left-10"
@onConfirm="handleDel(scope.row)"
title="确定要删除用户吗?">
<el-button
slot="reference"
size="mini"
type="danger">删除
</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div class="sh-20"></div>
<el-pagination
background
layout="prev, pager, next"
:pager-count="11"
@current-change="pageChange"
:current-page="page"
:total="count">
</el-pagination>
</el-card>
<el-dialog
title="OTP密钥"
:visible.sync="otpImgData.visible"
width="350px"
center>
<div style="text-align: center">{{ otpImgData.username }} : {{ otpImgData.nickname }}</div>
<img :src="otpImgData.base64Img" alt="otp-img"/>
</el-dialog>
<!--新增修改弹出框-->
<el-dialog
:close-on-click-modal="false"
title="用户"
:visible="user_edit_dialog"
@close="disVisible"
width="650px"
center>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm">
<el-form-item label="用户ID" prop="id">
<el-input v-model="ruleForm.id" disabled></el-input>
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="ruleForm.username" :disabled="ruleForm.id > 0"></el-input>
</el-form-item>
<el-form-item label="姓名" prop="nickname">
<el-input v-model="ruleForm.nickname"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="ruleForm.email"></el-input>
</el-form-item>
<el-form-item label="PIN码" prop="pin_code">
<el-input v-model="ruleForm.pin_code" placeholder="不填由系统自动生成"></el-input>
</el-form-item>
<el-form-item label="禁用OTP" prop="disable_otp">
<el-switch
v-model="ruleForm.disable_otp">
</el-switch>
</el-form-item>
<el-form-item label="OTP密钥" prop="otp_secret" v-if="!ruleForm.disable_otp">
<el-input v-model="ruleForm.otp_secret" placeholder="不填由系统自动生成"></el-input>
</el-form-item>
<el-form-item label="用户组" prop="groups">
<el-checkbox-group v-model="ruleForm.groups">
<el-checkbox v-for="(item) in grouNames" :key="item" :label="item" :name="item"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="发送邮件" prop="send_email">
<el-switch
v-model="ruleForm.send_email">
</el-switch>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="ruleForm.status">
<el-radio :label="1" border>启用</el-radio>
<el-radio :label="0" border>停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">保存</el-button>
<!-- <el-button @click="resetForm('ruleForm')">重置</el-button>-->
<el-button @click="disVisible">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "List",
components: {},
mixins: [],
created() {
this.$emit('update:route_path', this.$route.path)
this.$emit('update:route_name', ['用户信息', '用户列表'])
},
mounted() {
this.getGroups();
this.getData(1)
},
data() {
return {
page: 1,
grouNames: [],
tableData: [],
count: 10,
searchData: '',
otpImgData: {visible: false, username: '', nickname: '', base64Img: ''},
ruleForm: {
send_email: true,
status: 1,
groups: [],
},
rules: {
username: [
{required: true, message: '请输入用户名', trigger: 'blur'},
{max: 50, message: '长度小于 50 个字符', trigger: 'blur'}
],
nickname: [
{required: true, message: '请输入用户姓名', trigger: 'blur'}
],
email: [
{required: true, message: '请输入用户邮箱', trigger: 'blur'},
{type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']}
],
password: [
{min: 6, message: '长度大于 6 个字符', trigger: 'blur'}
],
pin_code: [
{min: 6, message: 'PIN码大于 6 个字符', trigger: 'blur'}
],
date1: [
{type: 'date', required: true, message: '请选择日期', trigger: 'change'}
],
groups: [
{type: 'array', required: true, message: '请至少选择一个组', trigger: 'change'}
],
status: [
{required: true}
],
},
}
},
methods: {
getOtpImg(row) {
// this.base64Img = Buffer.from(data).toString('base64');
this.otpImgData.visible = true
axios.get('/user/otp_qr', {
params: {
id: row.id,
b64: '1',
}
}).then(resp => {
var rdata = resp.data;
// console.log(resp);
this.otpImgData.username = row.username;
this.otpImgData.nickname = row.nickname;
this.otpImgData.base64Img = 'data:image/png;base64,' + rdata
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
handleDel(row) {
axios.post('/user/del?id=' + row.id).then(resp => {
var rdata = resp.data
if (rdata.code === 0) {
this.$message.success(rdata.msg);
this.getData(1);
} else {
this.$message.error(rdata.msg);
}
console.log(rdata);
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
handleEdit(row) {
!this.$refs['ruleForm'] || this.$refs['ruleForm'].resetFields();
console.log(row)
this.user_edit_dialog = true
if (!row) {
return;
}
axios.get('/user/detail', {
params: {
id: row.id,
}
}).then(resp => {
var data = resp.data.data
// 修改默认不发送邮件
data.send_email = false
this.ruleForm = data
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
handleSearch() {
console.log(this.searchData)
this.getData(1, this.searchData)
},
pageChange(p) {
this.getData(p)
},
getData(page, prefix) {
this.page = page
axios.get('/user/list', {
params: {
page: page,
prefix: prefix || '',
}
}).then(resp => {
var data = resp.data.data
console.log(data);
this.tableData = data.datas;
this.count = data.count
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
getGroups() {
axios.get('/group/names', {}).then(resp => {
var data = resp.data.data
console.log(data.datas);
this.grouNames = data.datas;
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (!valid) {
console.log('error submit!!');
return false;
}
// alert('submit!');
axios.post('/user/set', this.ruleForm).then(resp => {
var data = resp.data
if (data.code === 0) {
this.$message.success(data.msg);
this.getData(1);
} else {
this.$message.error(data.msg);
}
console.log(data);
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
},
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,196 @@
<template>
<div>
<el-card>
<el-table
ref="multipleTable"
:data="tableData"
border>
<el-table-column
sortable="true"
type="index"
label="序号"
width="50">
</el-table-column>
<el-table-column
prop="username"
label="用户名">
</el-table-column>
<el-table-column
prop="group"
label="登陆组">
</el-table-column>
<el-table-column
prop="mac_addr"
label="MAC地址">
</el-table-column>
<el-table-column
prop="ip"
label="IP地址"
width="140">
</el-table-column>
<el-table-column
prop="remote_addr"
label="远端地址">
</el-table-column>
<el-table-column
prop="tun_name"
label="虚拟网卡">
</el-table-column>
<el-table-column
prop="mtu"
label="MTU">
</el-table-column>
<el-table-column
prop="is_mobile"
label="客户端">
<template slot-scope="scope">
<i v-if="scope.row.client === 'mobile'" class="el-icon-mobile-phone" style="font-size: 20px;color: red"></i>
<i v-else class="el-icon-s-platform" style="font-size: 20px;color: blue"></i>
</template>
</el-table-column>
<el-table-column
prop="status"
label="实时 上行/下行"
width="220">
<template slot-scope="scope">
<el-tag type="success">{{ scope.row.bandwidth_up }}</el-tag>
<el-tag class="m-left-10">{{ scope.row.bandwidth_down }}</el-tag>
</template>
</el-table-column>
<el-table-column
prop="status"
label="总量 上行/下行"
width="200">
<template slot-scope="scope">
<el-tag effect="dark" type="success">{{ scope.row.bandwidth_up_all }}</el-tag>
<el-tag class="m-left-10" effect="dark">{{ scope.row.bandwidth_down_all }}</el-tag>
</template>
</el-table-column>
<el-table-column
prop="last_login"
label="登陆时间"
:formatter="tableDateFormat">
</el-table-column>
<el-table-column
label="操作"
width="150">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
@click="handleReline(scope.row)">重连
</el-button>
<el-popconfirm
class="m-left-10"
@onConfirm="handleOffline(scope.row)"
title="确定要下线用户吗?">
<el-button
slot="reference"
size="mini"
type="danger">下线
</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "Online",
components: {},
mixins: [],
created() {
this.$emit('update:route_path', this.$route.path)
this.$emit('update:route_name', ['用户信息', '在线用户'])
},
mounted() {
this.getData();
const chatTimer = setInterval(() => {
this.getData();
}, 2000);
this.$once('hook:beforeDestroy', () => {
clearInterval(chatTimer);
})
},
data() {
return {
tableData: [],
}
},
methods: {
handleOffline(row) {
axios.post('/user/offline?token=' + row.token).then(resp => {
var data = resp.data
if (data.code === 0) {
this.$message.success(data.msg);
this.getData();
} else {
this.$message.error(data.msg);
}
console.log(data);
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
handleReline(row) {
axios.post('/user/reline?token=' + row.token).then(resp => {
var data = resp.data
if (data.code === 0) {
this.$message.success(data.msg);
this.getData();
} else {
this.$message.error(data.msg);
}
console.log(data);
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
handleEdit(a, row) {
console.log(a, row)
},
getData() {
axios.get('/user/online').then(resp => {
var data = resp.data.data
console.log(data);
this.tableData = data.datas;
this.count = data.count
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
});
},
},
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,5 @@
import Vue from 'vue'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(Element)

51
web/src/plugins/mixin.js Normal file
View File

@@ -0,0 +1,51 @@
import Vue from "vue";
function gDateFormat(p) {
var da = new Date(p);
var year = da.getFullYear();
var month = da.getMonth() + 1;
var dt = da.getDate();
var h = da.getHours();
var m = da.getMinutes();
var s = da.getSeconds();
return year + '-' + month + '-' + dt + ' ' + h + ':' + m + ':' + s;
}
var Mixin = {
data() {
return {
user_edit_dialog: false,
isLoading: false,
}
},
computed: {},
methods: {
tableDateFormat(row, column) {
var p = row[column.property];
if (p === undefined) {
return "";
}
return gDateFormat(p);
},
tableArrayFormat(row, column) {
var p = row[column.property];
if (p === undefined) {
return "";
}
return p.join("\n\r\n");
},
disVisible() {
this.user_edit_dialog = false
},
},
}
// Vue.filter("dateFormat", function (p) {
// return gDateFormat(p);
// })
Vue.mixin(Mixin)
// export default Mixin

View File

@@ -0,0 +1,48 @@
// http://www.zhangwj.com/
// 全局的 axios 默认值
import axios from "axios";
import {getToken, removeToken} from "./token";
// axios.defaults.headers.common['Jwt'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
if (process.env.NODE_ENV !== 'production') {
// 开发环境
axios.defaults.baseURL = 'http://172.23.83.233:8800';
}
function request(vm) {
// HTTP 请求拦截器
axios.interceptors.request.use(config => {
// 在发送请求之前做些什么
// 获取token, 并添加到 headers 请求头中
const token = getToken();
if (token) {
config.headers.Jwt = token;
}
return config;
});
console.log(vm)
// HTTP 响应拦截器
// 统一处理 401 状态token 过期的处理清除token跳转login
// 参数 1 表示成功响应
axios.interceptors.response.use(null, err => {
// 没有登录或令牌过期
if (err.response.status === 401) {
// 注销情况状态和token
// vm.$store.dispatch("logout");
// 跳转的登录页
removeToken();
vm.$router.push('/login');
// 注意: 这里的 vm 实例需要外部传入
}
return Promise.reject(err);
});
}
export default request

74
web/src/plugins/router.js Normal file
View File

@@ -0,0 +1,74 @@
import Vue from "vue";
import VueRouter from "vue-router";
import {getToken} from "./token";
Vue.use(VueRouter)
const routes = [
{path: '/login', component: () => import('@/pages/Login')},
{
path: '/admin',
component: () => import('@/layout/Layout'),
redirect: '/admin/home',
children: [
{path: 'home', component: () => import('@/pages/Home')},
{path: 'set/system', component: () => import('@/pages/set/System')},
{path: 'set/soft', component: () => import('@/pages/set/Soft')},
{path: 'set/other', component: () => import('@/pages/set/Other')},
{path: 'user/list', component: () => import('@/pages/user/List')},
{path: 'user/online', component: () => import('@/pages/user/Online')},
{path: 'user/ip_map', component: () => import('@/pages/user/IpMap')},
{path: 'group/list', component: () => import('@/pages/group/List')},
],
},
{path: '*', redirect: '/admin/home'},
]
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
// 判断要进入的路由是否需要认证
const token = getToken();
console.log("beforeEach", from.path, to.path, token)
// console.log(from)
// 没有token,全都跳转到login
if (!token) {
if (to.path === "/login") {
next();
return;
}
next({
path: '/login',
query: {
redirect: to.path
}
});
return;
}
if (to.path === "/login") {
next({path: '/admin/home'});
return;
}
// 有token情况下
next();
});
export default router;

22
web/src/plugins/token.js Normal file
View File

@@ -0,0 +1,22 @@
const tokenKey = 'AnyLink-Jwt-Token'
const tokenUser = 'AnyLink-Jwt-User'
export function getToken() {
return localStorage.getItem(tokenKey)
}
export function setToken(token) {
return localStorage.setItem(tokenKey, token)
}
export function setUser(username) {
return localStorage.setItem(tokenUser, username)
}
export function getUser() {
return localStorage.getItem(tokenUser)
}
export function removeToken() {
return localStorage.removeItem(tokenKey)
}