增加账号管理和日志的基本内容

This commit is contained in:
samwaf
2023-02-02 11:21:28 +08:00
parent 0374af52a5
commit 43841a3c42
26 changed files with 1530 additions and 2 deletions

View File

@@ -14,6 +14,8 @@ type APIGroup struct {
WafAntiCCApi
WafBlockIpApi
WafBlockUrlApi
WafAccountApi
WafAccountLogApi
}
var APIGroupAPP = new(APIGroup)
@@ -29,4 +31,7 @@ var (
wafIpBlockService = waf_service.WafBlockIpServiceApp
wafUrlBlockService = waf_service.WafBlockUrlServiceApp
wafAccountService = waf_service.WafAccountServiceApp
wafAccountLogService = waf_service.WafAccountLogServiceApp
)

94
api/waf_account.go Normal file
View File

@@ -0,0 +1,94 @@
package api
import (
"SamWaf/model/common/response"
"SamWaf/model/request"
"errors"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type WafAccountApi struct {
}
func (w *WafAccountApi) AddApi(c *gin.Context) {
var req request.WafAccountAddReq
err := c.ShouldBind(&req)
if err == nil {
err = wafAccountService.CheckIsExistApi(req)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
err = wafAccountService.AddApi(req)
if err == nil {
response.OkWithMessage("添加成功", c)
} else {
response.FailWithMessage("添加失败", c)
}
return
} else {
response.FailWithMessage("当前数据已经存在", c)
return
}
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafAccountApi) GetDetailApi(c *gin.Context) {
var req request.WafAccountDetailReq
err := c.ShouldBind(&req)
if err == nil {
bean := wafAccountService.GetDetailApi(req)
response.OkWithDetailed(bean, "获取成功", c)
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafAccountApi) GetListApi(c *gin.Context) {
var req request.WafAccountSearchReq
err := c.ShouldBind(&req)
if err == nil {
beans, total, _ := wafAccountService.GetListApi(req)
response.OkWithDetailed(response.PageResult{
List: beans,
Total: total,
PageIndex: req.PageIndex,
PageSize: req.PageSize,
}, "获取成功", c)
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafAccountApi) DelAccountApi(c *gin.Context) {
var req request.WafAccountDelReq
err := c.ShouldBind(&req)
if err == nil {
err = wafAccountService.DelApi(req)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
response.FailWithMessage("请检测参数", c)
} else if err != nil {
response.FailWithMessage("发生错误", c)
} else {
response.FailWithMessage("删除成功", c)
}
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafAccountApi) ModifyAccountApi(c *gin.Context) {
var req request.WafAccountEditReq
err := c.ShouldBind(&req)
if err == nil {
err = wafAccountService.ModifyApi(req)
if err != nil {
response.FailWithMessage("编辑发生错误", c)
} else {
response.OkWithMessage("编辑成功", c)
}
} else {
response.FailWithMessage("解析失败", c)
}
}

36
api/waf_account_log.go Normal file
View File

@@ -0,0 +1,36 @@
package api
import (
"SamWaf/model/common/response"
"SamWaf/model/request"
"github.com/gin-gonic/gin"
)
type WafAccountLogApi struct {
}
func (w *WafAccountLogApi) GetDetailApi(c *gin.Context) {
var req request.WafAccountLogDetailReq
err := c.ShouldBind(&req)
if err == nil {
bean := wafAccountLogService.GetDetailApi(req)
response.OkWithDetailed(bean, "获取成功", c)
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafAccountLogApi) GetListApi(c *gin.Context) {
var req request.WafAccountLogSearchReq
err := c.ShouldBind(&req)
if err == nil {
beans, total, _ := wafAccountLogService.GetListApi(req)
response.OkWithDetailed(response.PageResult{
List: beans,
Total: total,
PageIndex: req.PageIndex,
PageSize: req.PageSize,
}, "获取成功", c)
} else {
response.FailWithMessage("解析失败", c)
}
}

View File

@@ -39,6 +39,9 @@ func InitDb() {
//抵抗CC
db.AutoMigrate(&model.AntiCC{})
//waf自生账号
db.AutoMigrate(&model.Account{})
db.AutoMigrate(&model.AccountLog{})
global.GWAF_LOCAL_DB.Callback().Query().Before("gorm:query").Register("tenant_plugin:before_query", before_query)
//重启需要删除无效规则

View File

@@ -25,7 +25,8 @@ func InitRouter(r *gin.Engine) {
router.ApiGroupApp.InitAntiCCRouter(RouterGroup)
router.ApiGroupApp.InitBlockIpRouter(RouterGroup)
router.ApiGroupApp.InitBlockUrlRouter(RouterGroup)
router.ApiGroupApp.InitAccountRouter(RouterGroup)
router.ApiGroupApp.InitAccountLogRouter(RouterGroup)
}
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {

View File

@@ -0,0 +1,19 @@
import request from '@/utils/request'
//查询所有账号列表
export function account_list_api(params) {
return request({
url: 'account/list',
method: 'get',
params: params
})
}
//查询所有账号操作日志列表
export function account_log_list_api(params) {
return request({
url: 'account_log/list',
method: 'get',
params: params
})
}

View File

@@ -0,0 +1,514 @@
<template>
<div>
<t-card class="list-card-container">
<t-row justify="space-between">
<div class="left-operation-container">
<t-button @click="handleAddAccount"> 新建账号 </t-button>
<t-button variant="base" theme="default" :disabled="!selectedRowKeys.length"> 导出日志 </t-button>
<p v-if="!!selectedRowKeys.length" class="selected-count">已选{{ selectedRowKeys.length }}</p>
</div>
<t-input v-model="searchValue" class="search-input" placeholder="请输入你需要搜索的账号" clearable>
<template #suffix-icon>
<search-icon size="20px" />
</template>
</t-input>
</t-row>
<div class="table-container">
<t-table :columns="columns" :data="data" :rowKey="rowKey" :verticalAlign="verticalAlign" :hover="hover"
:pagination="pagination" :selected-row-keys="selectedRowKeys" :loading="dataLoading"
@page-change="rehandlePageChange" @change="rehandleChange" @select-change="rehandleSelectChange"
:headerAffixedTop="true" :headerAffixProps="{ offsetTop: offsetTop, container: getContainer }">
<template #op="slotProps">
<a class="t-button-link" @click="handleClickDetail(slotProps)">详情</a>
<a class="t-button-link" @click="handleClickEdit(slotProps)">编辑</a>
<a class="t-button-link" @click="handleClickDelete(slotProps)">删除</a>
</template>
</t-table>
</div>
<div>
<router-view></router-view>
</div>
</t-card>
<!-- 新建账号弹窗 -->
<t-dialog header="新建账号" :visible.sync="addFormVisible" :width="680" :footer="false">
<div slot="body">
<!-- 表单内容 -->
<t-form :data="formData" ref="form" :rules="rules" @submit="onSubmit" :labelWidth="100">
<t-form-item label="登录账号" name="login_account">
<t-input :style="{ width: '480px' }" v-model="formData.login_account" placeholder="请输入登录账号"></t-input>
</t-form-item>
<t-form-item label="登录密码" name="login_password">
<t-input :style="{ width: '480px' }" v-model="formData.login_password" placeholder="请输入登录密码"></t-input>
</t-form-item>
<t-form-item label="状态" name="rate">
<t-input-number :style="{ width: '480px' }" v-model="formData.status" placeholder="请输入状态"></t-input-number>
</t-form-item>
<t-form-item label="备注" name="remarks">
<t-textarea :style="{ width: '480px' }" v-model="formData.remarks" placeholder="请输入备注内容" name="remarks">
</t-textarea>
</t-form-item>
<t-form-item style="float: right">
<t-button variant="outline" @click="onClickCloseBtn">取消</t-button>
<t-button theme="primary" type="submit">确定</t-button>
</t-form-item>
</t-form>
</div>
</t-dialog>
<!-- 编辑账号弹窗 -->
<t-dialog header="编辑账号" :visible.sync="editFormVisible" :width="680" :footer="false">
<div slot="body">
<!-- 表单内容 -->
<t-form :data="formEditData" ref="form" :rules="rules" @submit="onSubmitEdit" :labelWidth="100">
<t-form-item label="登录账号" name="login_account">
<t-input :style="{ width: '480px' }" v-model="formEditData.login_account" placeholder="请输入登录账号"></t-input>
</t-form-item>
<t-form-item label="登录密码" name="login_password">
<t-input :style="{ width: '480px' }" v-model="formEditData.login_password" placeholder="请输入登录密码"></t-input>
</t-form-item>
<t-form-item label="状态" name="status">
<t-input-number :style="{ width: '480px' }" v-model="formEditData.status" placeholder="请输入状态"></t-input-number>
</t-form-item>
<t-form-item label="备注" name="remarks">
<t-textarea :style="{ width: '480px' }" v-model="formEditData.remarks" placeholder="请输入内容" name="remarks">
</t-textarea>
</t-form-item>
<t-form-item style="float: right">
<t-button variant="outline" @click="onClickCloseEditBtn">取消</t-button>
<t-button theme="primary" type="submit">确定</t-button>
</t-form-item>
</t-form>
</div>
</t-dialog>
<t-dialog header="确认删除当前所选数据?" :body="confirmBody" :visible.sync="confirmVisible" @confirm="onConfirmDelete"
:onCancel="onCancel">
</t-dialog>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import {
SearchIcon
} from 'tdesign-icons-vue';
import Trend from '@/components/trend/index.vue';
import {
prefix
} from '@/config/global';
import {
account_list_api
} from '@/apis/account';
import {
SSL_STATUS,
GUARD_STATUS,
CONTRACT_STATUS,
CONTRACT_STATUS_OPTIONS,
CONTRACT_TYPES,
CONTRACT_PAYMENT_TYPES
} from '@/constants';
const INITIAL_DATA = {
login_account: '',
login_password: '',
status: 1,
remarks: '',
};
export default Vue.extend({
name: 'ListBase',
components: {
SearchIcon,
Trend,
},
data() {
return {
addFormVisible: false,
editFormVisible: false,
guardVisible: false,
confirmVisible: false,
formData: {
...INITIAL_DATA
},
formEditData: {
...INITIAL_DATA
},
rules: {
login_account: [{
required: true,
message: '请输入登录账号',
type: 'error'
}],
login_password: [{
required: true,
message: '请输入登录密码',
type: 'error'
}],
},
textareaValue: '',
prefix,
dataLoading: false,
data: [], //列表数据信息
detail_data: [], //加载详情信息用于编辑
selectedRowKeys: [],
value: 'first',
columns: [{
colKey: 'row-select',
type: 'multiple',
width: 64,
fixed: 'left'
},
{
title: '登录账号',
align: 'left',
width: 250,
ellipsis: true,
colKey: 'login_account',
fixed: 'left',
},
{
title: '登录密码',
width: 200,
ellipsis: true,
colKey: 'login_password',
},
{
title: '备注',
width: 200,
ellipsis: true,
colKey: 'remarks',
},
{
title: '添加时间',
width: 200,
ellipsis: true,
colKey: 'create_time',
},
{
align: 'left',
fixed: 'right',
width: 200,
colKey: 'op',
title: '操作',
},
],
rowKey: 'code',
tableLayout: 'auto',
verticalAlign: 'top',
hover: true,
rowClassName: (rowKey: string) => `${rowKey}-class`,
// 与pagination对齐
pagination: {
total: 0,
current: 1,
pageSize: 10
},
searchValue: '',
//索引区域
deleteIdx: -1,
guardStatusIdx :-1,
};
},
computed: {
confirmBody() {
if (this.deleteIdx > -1) {
const {
url
} = this.data?. [this.deleteIdx];
return `删除后,${url}的数据将被删除,且无法恢复`;
}
return '';
},
offsetTop() {
return this.$store.state.setting.isUseTabsRouter ? 48 : 0;
},
},
mounted() {
this.getList("")
this.loadHostList()
},
methods: {
loadHostList(){
/* let that = this;
allhost().then((res) => {
let resdata = res
console.log(resdata)
if (resdata.code === 0) {
that.host_options = resdata.data;
}
})
.catch((e: Error) => {
console.log(e);
}) */
},
getList(keyword) {
let that = this
this.$request
.get('/account/list', {
params: {
pageSize: that.pagination.pageSize,
pageIndex: that.pagination.current,
login_account: '',
}
})
.then((res) => {
let resdata = res
console.log(resdata)
if (resdata.code === 0) {
this.data = resdata.data.list;
this.data_attach = []
console.log('getList',this.data)
this.pagination = {
...this.pagination,
total: resdata.data.total,
};
}
})
.catch((e: Error) => {
console.log(e);
})
.finally(() => {
this.dataLoading = false;
});
this.dataLoading = true;
},
getContainer() {
return document.querySelector('.tdesign-starter-layout');
},
rehandlePageChange(curr, pageInfo) {
console.log('分页变化', curr, pageInfo);
this.pagination.current = curr.current
if (this.pagination.pageSize != curr.pageSize) {
this.pagination.current = 1
this.pagination.pageSize = curr.pageSize
}
this.getList("")
},
rehandleSelectChange(selectedRowKeys: number[]) {
this.selectedRowKeys = selectedRowKeys;
},
rehandleChange(changeParams, triggerAndData) {
console.log('统一Change', changeParams, triggerAndData);
},
handleClickDetail(e) {
console.log(e)
const {
id
} = e.row
console.log('list',id)
this.$router.push({
path: '/account/detail',
query: {
id: id,
},
}, );
},
handleClickEdit(e) {
console.log(e)
const {
id
} = e.row
console.log(id)
this.editFormVisible = true
this.getDetail(id)
},
handleAddAccount() {
//添加
this.addFormVisible = true
this.formData = {
login_account: '',
login_password: '',
remarks: '',
status:1,
};
},
onSubmit({
result,
firstError
}): void {
let that = this
if (!firstError) {
let postdata = {
...that.formData
}
this.$request
.post('/account/add', {
...postdata
})
.then((res) => {
let resdata = res
console.log(resdata)
if (resdata.code === 0) {
that.$message.success(resdata.msg);
that.addFormVisible = false;
that.pagination.current = 1
that.getList("")
} else {
that.$message.warning(resdata.msg);
}
})
.catch((e: Error) => {
console.log(e);
})
.finally(() => {});
} else {
console.log('Errors: ', result);
that.$message.warning(firstError);
}
},
onSubmitEdit({
result,
firstError
}): void {
let that = this
if (!firstError) {
let postdata = {
...that.formEditData
}
this.$request
.post('/account/edit', {
...postdata
})
.then((res) => {
let resdata = res
console.log(resdata)
if (resdata.code === 0) {
that.$message.success(resdata.msg);
that.editFormVisible = false;
that.pagination.current = 1
that.getList("")
} else {
that.$message.warning(resdata.msg);
}
})
.catch((e: Error) => {
console.log(e);
})
.finally(() => {});
} else {
console.log('Errors: ', result);
that.$message.warning(firstError);
}
},
onClickCloseBtn(): void {
this.formVisible = false;
this.formData = {};
},
onClickCloseEditBtn(): void {
this.editFormVisible = false;
this.formEditData = {};
},
handleClickDelete(row) {
console.log(row)
this.deleteIdx = row.rowIndex;
this.confirmVisible = true;
},
onConfirmDelete() {
this.confirmVisible = false;
console.log('delete', this.data)
console.log('delete', this.data[this.deleteIdx])
let {
id
} = this.data[this.deleteIdx]
let that = this
this.$request
.get('/account/del', {
params: {
id: id,
}
})
.then((res) => {
let resdata = res
console.log(resdata)
if (resdata.code === 0) {
that.pagination.current = 1
that.getList("")
that.$message.success(resdata.msg);
} else {
that.$message.warning(resdata.msg);
}
})
.catch((e: Error) => {
console.log(e);
})
.finally(() => {});
this.resetIdx();
},
onCancel() {
this.resetIdx();
},
resetIdx() {
this.deleteIdx = -1;
},
getDetail(id) {
let that = this
this.$request
.get('/account/detail', {
params: {
id: id,
}
})
.then((res) => {
let resdata = res
console.log(resdata)
if (resdata.code === 0) {
that.detail_data = resdata.data;
that.formEditData = {
...that.detail_data
}
}
})
.catch((e: Error) => {
console.log(e);
})
.finally(() => {});
},
},
});
</script>
<style lang="less" scoped>
@import '@/style/variables';
.payment-col {
display: flex;
.trend-container {
display: flex;
align-items: center;
margin-left: 8px;
}
}
.left-operation-container {
padding: 0 0 6px 0;
margin-bottom: 16px;
.selected-count {
display: inline-block;
margin-left: 8px;
color: var(--td-text-color-secondary);
}
}
.search-input {
width: 360px;
}
.t-button+.t-button {
margin-left: @spacer;
}
</style>

View File

@@ -0,0 +1,106 @@
@import '@/style/variables.less';
.detail-base {
/deep/ .t-card {
padding: 8px;
}
/deep/ .t-card__title {
font-size: 20px;
font-weight: 500;
}
&-info-steps {
padding-top: 12px;
}
}
.info-block {
column-count: 2;
.info-item {
padding: 12px 0;
display: flex;
color: var(--td-text-color-primary);
h1 {
width: 200px;
font-weight: normal;
font-size: @font-size-base;
color: var(--td-text-color-secondary);
text-align: left;
line-height: 22px;
@media (max-width: @screen-sm-max) {
width: 100px;
}
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
width: 120px;
}
}
span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-left: 24px;
}
i {
display: inline-block;
width: 8px;
height: 8px;
border-radius: @border-radius-50;
background: var(--td-success-color-5);
}
.inProgress {
color: var(--td-success-color-5);
}
.pdf {
color: var(--td-brand-color);
&:hover {
cursor: pointer;
}
}
}
}
.dialog-info-block {
.info-item {
padding: 12px 0;
display: flex;
h1 {
width: 84px;
font-family: PingFangSC-Regular;
font-size: 14px;
color: var(--td-text-color-secondary);
text-align: left;
line-height: 22px;
}
span {
margin-left: 24px;
}
i {
display: inline-block;
width: 8px;
height: 8px;
border-radius: @border-radius-50;
background: var(--td-success-color-5);
}
.green {
color: var(--td-success-color-5);
}
.blue {
color: var(--td-brand-color-8);
}
}
}

View File

@@ -0,0 +1,148 @@
<template>
<div class="detail-base">
<t-card title="网站详情">
<div class="info-block">
<div class="info-item">
<h1> 创建时间</h1>
<span>
{{ detail_data.create_time }}
</span>
</div>
<div class="info-item">
<h1> 网站</h1>
<span>
{{ detail_data.host }}
</span>
</div>
<div class="info-item">
<h1> 网站端口</h1>
<span>
{{ detail_data.port }}
</span>
</div>
<div class="info-item">
<h1> 加密证书</h1>
<span>
{{ detail_data.ssl }}
</span>
</div>
<div class="info-item">
<h1> 后端系统类型</h1>
<span>
{{ detail_data.remote_system }}
</span>
</div>
<div class="info-item">
<h1> 后端系统应用类型</h1>
<span>
{{ detail_data.remote_app }}
</span>
</div>
<div class="info-item">
<h1> 后端域名</h1>
<span>
{{ detail_data.remote_host }}
</span>
</div>
<div class="info-item">
<h1> 后端端口</h1>
<span>
{{ detail_data.remote_port }}
</span>
</div>
</div>
</t-card>
<t-card title="最近配置记录" class="container-base-margin-top">
<t-list :split="true">
<t-list-item>
<t-list-item-meta title="请求头" :description="detail_data.header"></t-list-item-meta>
</t-list-item>
<t-list-item>
<t-list-item-meta title="请求用户浏览器" :description="detail_data.user_agent"></t-list-item-meta>
</t-list-item>
<t-list-item>
<t-list-item-meta title="请求cookies" :description="detail_data.cookies"></t-list-item-meta>
</t-list-item>
<t-list-item>
<t-list-item-meta title="请求BODY" :description="detail_data.body"></t-list-item-meta>
</t-list-item>
</t-list>
</t-card>
</div>
</template>
<script lang="ts">
import {
prefix
} from '@/config/global';
import model from '@/service/service-detail-base';
export default {
name: 'WafHostDetail',
data() {
return {
prefix,
baseInfoData: model.getBaseInfoData(),
detail_data: {}
};
},
beforeRouteUpdate(to, from) {
console.log('beforeRouteUpdate')
},
mounted() {
console.log('----mounted----')
this.getDetail(this.$route.query.code);
},
beforeCreate() {
console.log('----beforeCreate----')
},
created() {
console.log('----created----')
},
beforeMount() {
console.log('----beforeMount----')
},
beforeUpdate() {
console.log('----beforeUpdate----')
},
updated() {
console.log('----updated----')
},
watch: {
'$route.query.code'(newVal, oldVal) {
console.log('route.query.code changed', newVal, oldVal)
this.getDetail(newVal)
},
},
methods: {
getDetail(id) {
let that = this
this.$request
.get('/wafhost/host/detail', {
params: {
CODE: id,
}
})
.then((res) => {
let resdata = res
console.log(resdata)
if (resdata.code === 0) {
that.detail_data = resdata.data;
}
})
.catch((e: Error) => {
console.log(e);
})
.finally(() => {});
},
},
};
</script>
<style lang="less" scoped>
@import './index';
</style>

View File

@@ -0,0 +1,351 @@
<template>
<div>
<t-card class="list-card-container">
<t-row justify="space-between">
<div class="left-operation-container">
<t-button variant="base" theme="default" :disabled="!selectedRowKeys.length"> 导出日志 </t-button>
<p v-if="!!selectedRowKeys.length" class="selected-count">已选{{ selectedRowKeys.length }}</p>
</div>
<t-input v-model="searchValue" class="search-input" placeholder="请输入你需要搜索的攻击日志" clearable>
<template #suffix-icon>
<search-icon size="20px" />
</template>
</t-input>
</t-row>
<div class="table-container">
<t-table :columns="columns" :data="data" :rowKey="rowKey" :verticalAlign="verticalAlign" :hover="hover"
:pagination="pagination" :selected-row-keys="selectedRowKeys" :loading="dataLoading"
@page-change="rehandlePageChange" @change="rehandleChange" @select-change="rehandleSelectChange"
:headerAffixedTop="true" :headerAffixProps="{ offsetTop: offsetTop, container: getContainer }">
<template #op="slotProps">
<a class="t-button-link" @click="handleClickDetail(slotProps)">详情</a>
<a class="t-button-link" @click="handleClickEdit(slotProps)">编辑</a>
</template>
</t-table>
</div>
<div>
<router-view></router-view>
</div>
</t-card>
<!-- 编辑Url白名单弹窗 -->
<t-dialog header="查看日志" :visible.sync="editFormVisible" :width="680" :footer="false">
<div slot="body">
<!-- 表单内容 -->
<t-form :data="formEditData" ref="form" :rules="rules" :labelWidth="100">
<t-form-item label="操作账号" name="login_account">
<t-input :style="{ width: '480px' }" v-model="formEditData.login_account" placeholder="操作账号"></t-input>
</t-form-item>
<t-form-item label="操作类型" name="op_type">
<t-input :style="{ width: '480px' }" v-model="formEditData.op_type" placeholder="请输入操作类型"></t-input>
</t-form-item>
<t-form-item label="操作备注" name="op_content">
<t-textarea :style="{ width: '480px' }" v-model="formEditData.op_content" placeholder="请输入备注" name="op_content">
</t-textarea>
</t-form-item>
<t-form-item style="float: right">
<t-button variant="outline" @click="onClickCloseEditBtn">取消</t-button>
</t-form-item>
</t-form>
</div>
</t-dialog>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import {
SearchIcon
} from 'tdesign-icons-vue';
import Trend from '@/components/trend/index.vue';
import {
prefix
} from '@/config/global';
import {
account_log_list_api
} from '@/apis/account';
import {
SSL_STATUS,
GUARD_STATUS,
CONTRACT_STATUS,
CONTRACT_STATUS_OPTIONS,
CONTRACT_TYPES,
CONTRACT_PAYMENT_TYPES
} from '@/constants';
const INITIAL_DATA = {
login_account: '',
op_type: '',
op_content: '',
};
export default Vue.extend({
name: 'ListBase',
components: {
SearchIcon,
Trend,
},
data() {
return {
addFormVisible: false,
editFormVisible: false,
guardVisible: false,
confirmVisible: false,
formData: {
...INITIAL_DATA
},
formEditData: {
...INITIAL_DATA
},
rules: {
},
textareaValue: '',
prefix,
dataLoading: false,
data: [], //列表数据信息
detail_data: [], //加载详情信息用于编辑
selectedRowKeys: [],
value: 'first',
columns: [{
colKey: 'row-select',
type: 'multiple',
width: 64,
fixed: 'left'
},
{
title: '操作账号',
align: 'left',
width: 250,
ellipsis: true,
colKey: 'login_account',
fixed: 'left',
},
{
title: '操作类型',
width: 200,
ellipsis: true,
colKey: 'op_type',
},
{
title: '操作内容',
width: 200,
ellipsis: true,
colKey: 'op_content',
},
{
title: '添加时间',
width: 200,
ellipsis: true,
colKey: 'create_time',
},
{
align: 'left',
fixed: 'right',
width: 200,
colKey: 'op',
title: '操作',
},
],
rowKey: 'code',
tableLayout: 'auto',
verticalAlign: 'top',
hover: true,
rowClassName: (rowKey: string) => `${rowKey}-class`,
// 与pagination对齐
pagination: {
total: 0,
current: 1,
pageSize: 10
},
searchValue: '',
//索引区域
deleteIdx: -1,
guardStatusIdx :-1,
//主机列表
host_options:[]
};
},
computed: {
confirmBody() {
if (this.deleteIdx > -1) {
const {
url
} = this.data?. [this.deleteIdx];
return `删除后,${url}的数据将被删除,且无法恢复`;
}
return '';
},
offsetTop() {
return this.$store.state.setting.isUseTabsRouter ? 48 : 0;
},
},
mounted() {
this.getList("")
this.loadHostList()
},
methods: {
loadHostList(){
/* let that = this;
allhost().then((res) => {
let resdata = res
console.log(resdata)
if (resdata.code === 0) {
that.host_options = resdata.data;
}
})
.catch((e: Error) => {
console.log(e);
}) */
},
getList(keyword) {
let that = this
this.$request
.get('/account_log/list', {
params: {
pageSize: that.pagination.pageSize,
pageIndex: that.pagination.current,
login_account: '',
}
})
.then((res) => {
let resdata = res
console.log(resdata)
if (resdata.code === 0) {
this.data = resdata.data.list;
this.data_attach = []
console.log('getList',this.data)
this.pagination = {
...this.pagination,
total: resdata.data.total,
};
}
})
.catch((e: Error) => {
console.log(e);
})
.finally(() => {
this.dataLoading = false;
});
this.dataLoading = true;
},
getContainer() {
return document.querySelector('.tdesign-starter-layout');
},
rehandlePageChange(curr, pageInfo) {
console.log('分页变化', curr, pageInfo);
this.pagination.current = curr.current
if (this.pagination.pageSize != curr.pageSize) {
this.pagination.current = 1
this.pagination.pageSize = curr.pageSize
}
this.getList("")
},
rehandleSelectChange(selectedRowKeys: number[]) {
this.selectedRowKeys = selectedRowKeys;
},
rehandleChange(changeParams, triggerAndData) {
console.log('统一Change', changeParams, triggerAndData);
},
handleClickDetail(e) {
console.log(e)
const {
id
} = e.row
console.log('hostlist',id)
this.$router.push({
path: '/waf-host/anticc/detail',
query: {
id: id,
},
}, );
},
handleClickEdit(e) {
console.log(e)
const {
id
} = e.row
console.log(id)
this.editFormVisible = true
this.getDetail(id)
},
onClickCloseBtn(): void {
this.formVisible = false;
this.formData = {};
},
onClickCloseEditBtn(): void {
this.editFormVisible = false;
this.formEditData = {};
},
onCancel() {
this.resetIdx();
},
resetIdx() {
this.deleteIdx = -1;
},
getDetail(id) {
let that = this
this.$request
.get('/account_log/detail', {
params: {
id: id,
}
})
.then((res) => {
let resdata = res
console.log(resdata)
if (resdata.code === 0) {
that.detail_data = resdata.data;
that.formEditData = {
...that.detail_data
}
}
})
.catch((e: Error) => {
console.log(e);
})
.finally(() => {});
},
},
});
</script>
<style lang="less" scoped>
@import '@/style/variables';
.payment-col {
display: flex;
.trend-container {
display: flex;
align-items: center;
margin-left: 8px;
}
}
.left-operation-container {
padding: 0 0 6px 0;
margin-bottom: 16px;
.selected-count {
display: inline-block;
margin-left: 8px;
color: var(--td-text-color-secondary);
}
}
.search-input {
width: 360px;
}
.t-button+.t-button {
margin-left: @spacer;
}
</style>

View File

@@ -25,6 +25,29 @@ export default [
},
],
},
{
path: '/account',
name: 'account',
component: Layout,
redirect: '/account',
meta: { title: '账号', icon: ViewModuleIcon },
children: [
{
path: 'Account',
name: 'Account',
component: () => import('@/pages/waf/account/index.vue'),
meta: { title: '账号管理' },
},
{
path: 'AccountLog',
name: 'AccountLog',
component: () => import('@/pages/waf/accountlog/index.vue'),
meta: { title: '账号操作日志' },
},
],
},
{
path: '/waf-host',
name: 'wafhost',
@@ -82,7 +105,7 @@ export default [
component: () => import('@/pages/waf/urlwhite/detail/index.vue'),
meta: { title: 'Url白名单详情', hidden: true},
},
{
path: 'wafipBlocklist',
name: 'WafIpBlockList',

25
model/account.go Normal file
View File

@@ -0,0 +1,25 @@
package model
import "time"
type Account struct {
Id string `gorm:"primary_key" json:"id"`
UserCode string `json:"user_code"` //用户码(主要键)
TenantId string `json:"tenant_id"` //租户ID主要键
LoginAccount string `json:"login_account"` //登录账号
LoginPassword string `json:"login_password"` //密码md5加密
Status int `json:"status"` //状态
Remarks string `json:"remarks"` //备注
CreateTime time.Time `json:"create_time"` //创建时间
LastUpdateTime time.Time `json:"last_update_time"` //上次更新时间
}
type AccountLog struct {
Id string `gorm:"primary_key" json:"id"`
UserCode string `json:"user_code"` //用户码(主要键)
TenantId string `json:"tenant_id"` //租户ID主要键
LoginAccount string `json:"login_account"` //登录账号
OpType string `json:"op_type"` //操作类型
OpContent string `json:"op_content"` //操作内容
CreateTime time.Time `json:"create_time"` //创建时间
}

View File

@@ -0,0 +1,8 @@
package request
type WafAccountAddReq struct {
LoginAccount string `json:"login_account" form:"login_account"` //登录账号
LoginPassword string `json:"login_password" form:"login_password"` //密码md5加密
Status int `json:"status" form:"status" ` //状态
Remarks string `json:"remarks" form:"remarks" ` //备注
}

View File

@@ -0,0 +1,5 @@
package request
type WafAccountDelReq struct {
Id string `json:"id" form:"id"` //唯一键
}

View File

@@ -0,0 +1,5 @@
package request
type WafAccountDetailReq struct {
Id string `json:"id" form:"id"` //唯一键
}

View File

@@ -0,0 +1,9 @@
package request
type WafAccountEditReq struct {
Id string `json:"id"`
LoginAccount string `json:"login_account" form:"login_account"` //登录账号TODO 账号是否能随便改)
LoginPassword string `json:"login_password" form:"login_password"` //密码md5加密
Status int `json:"status" form:"status" ` //状态
Remarks string `json:"remarks" form:"remarks" ` //备注
}

View File

@@ -0,0 +1,5 @@
package request
type WafAccountLogDetailReq struct {
Id string `json:"id" form:"id"` //唯一键
}

View File

@@ -0,0 +1,8 @@
package request
import "SamWaf/model/common/request"
type WafAccountLogSearchReq struct {
LoginAccount string `json:"login_account" form:"login_account"` //登录账号
request.PageInfo
}

View File

@@ -0,0 +1,8 @@
package request
import "SamWaf/model/common/request"
type WafAccountSearchReq struct {
LoginAccount string `json:"login_account" form:"login_account"` //登录账号
request.PageInfo
}

View File

@@ -12,6 +12,8 @@ type ApiGroup struct {
AntiCCRouter
BlockIpRouter
BlockUrlRouter
AccountRouter
AccountLogRouter
}
var ApiGroupApp = new(ApiGroup)

19
router/waf_account.go Normal file
View File

@@ -0,0 +1,19 @@
package router
import (
"SamWaf/api"
"github.com/gin-gonic/gin"
)
type AccountRouter struct {
}
func (receiver *AccountRouter) InitAccountRouter(group *gin.RouterGroup) {
api := api.APIGroupAPP.WafAccountApi
router := group.Group("")
router.GET("/samwaf/account/list", api.GetListApi)
router.GET("/samwaf/account/detail", api.GetDetailApi)
router.POST("/samwaf/account/add", api.AddApi)
router.GET("/samwaf/account/del", api.DelAccountApi)
router.POST("/samwaf/account/edit", api.ModifyAccountApi)
}

16
router/waf_account_log.go Normal file
View File

@@ -0,0 +1,16 @@
package router
import (
"SamWaf/api"
"github.com/gin-gonic/gin"
)
type AccountLogRouter struct {
}
func (receiver *AccountLogRouter) InitAccountLogRouter(group *gin.RouterGroup) {
api := api.APIGroupAPP.WafAccountLogApi
router := group.Group("")
router.GET("/samwaf/account_log/list", api.GetListApi)
router.GET("/samwaf/account_log/detail", api.GetDetailApi)
}

View File

@@ -0,0 +1,77 @@
package waf_service
import (
"SamWaf/global"
"SamWaf/model"
"SamWaf/model/request"
"errors"
uuid "github.com/satori/go.uuid"
"time"
)
type WafAccountService struct{}
var WafAccountServiceApp = new(WafAccountService)
func (receiver *WafAccountService) AddApi(req request.WafAccountAddReq) error {
var bean = &model.Account{
Id: uuid.NewV4().String(),
UserCode: global.GWAF_USER_CODE,
TenantId: global.GWAF_TENANT_ID,
LoginAccount: req.LoginAccount,
LoginPassword: req.LoginPassword,
Status: req.Status,
Remarks: req.Remarks,
CreateTime: time.Now(),
LastUpdateTime: time.Now(),
}
global.GWAF_LOCAL_DB.Debug().Create(bean)
return nil
}
func (receiver *WafAccountService) CheckIsExistApi(req request.WafAccountAddReq) error {
return global.GWAF_LOCAL_DB.First(&model.Account{}, "login_account = ? ", req.LoginAccount).Error
}
func (receiver *WafAccountService) ModifyApi(req request.WafAccountEditReq) error {
var bean model.Account
global.GWAF_LOCAL_DB.Debug().Where("login_account = ?", req.LoginAccount).Find(&bean)
if bean.Id != "" && bean.LoginAccount != req.LoginAccount {
return errors.New("当前数据已经存在")
}
beanMap := map[string]interface{}{
"LoginAccount": req.LoginAccount,
"LoginPassword": req.LoginPassword,
"Status": req.Status,
"Remarks": req.Remarks,
"last_update_time": time.Now(),
}
err := global.GWAF_LOCAL_DB.Debug().Model(model.Account{}).Where("id = ?", req.Id).Updates(beanMap).Error
return err
}
func (receiver *WafAccountService) GetDetailApi(req request.WafAccountDetailReq) model.Account {
var bean model.Account
global.GWAF_LOCAL_DB.Debug().Where("id=?", req.Id).Find(&bean)
return bean
}
func (receiver *WafAccountService) GetDetailByIdApi(id string) model.Account {
var bean model.Account
global.GWAF_LOCAL_DB.Debug().Where("id=?", id).Find(&bean)
return bean
}
func (receiver *WafAccountService) GetListApi(req request.WafAccountSearchReq) ([]model.Account, int64, error) {
var bean []model.Account
var total int64 = 0
global.GWAF_LOCAL_DB.Debug().Limit(req.PageSize).Offset(req.PageSize * (req.PageIndex - 1)).Find(&bean)
global.GWAF_LOCAL_DB.Debug().Model(&model.Account{}).Count(&total)
return bean, total, nil
}
func (receiver *WafAccountService) DelApi(req request.WafAccountDelReq) error {
var bean model.Account
err := global.GWAF_LOCAL_DB.Where("id = ?", req.Id).First(&bean).Error
if err != nil {
return err
}
err = global.GWAF_LOCAL_DB.Where("id = ?", req.Id).Delete(model.Account{}).Error
return err
}

View File

@@ -0,0 +1,24 @@
package waf_service
import (
"SamWaf/global"
"SamWaf/model"
"SamWaf/model/request"
)
type WafAccountLogService struct{}
var WafAccountLogServiceApp = new(WafAccountLogService)
func (receiver *WafAccountLogService) GetDetailApi(req request.WafAccountLogDetailReq) model.AccountLog {
var bean model.AccountLog
global.GWAF_LOCAL_DB.Debug().Where("id=?", req.Id).Find(&bean)
return bean
}
func (receiver *WafAccountLogService) GetListApi(req request.WafAccountLogSearchReq) ([]model.AccountLog, int64, error) {
var bean []model.AccountLog
var total int64 = 0
global.GWAF_LOCAL_DB.Debug().Limit(req.PageSize).Offset(req.PageSize * (req.PageIndex - 1)).Find(&bean)
global.GWAF_LOCAL_DB.Debug().Model(&model.AccountLog{}).Count(&total)
return bean, total, nil
}

View File

@@ -2,7 +2,9 @@ package utils
import (
"SamWaf/model"
"net"
"strconv"
"strings"
"time"
)
@@ -63,3 +65,14 @@ func TimeToDayInt(t time.Time) int {
day, _ := strconv.Atoi(t.Format("20060102"))
return day
}
func GetPublicIP() string {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return "localhost"
// log.Fatal(err)
}
defer conn.Close()
localAddr := conn.LocalAddr().String()
idx := strings.LastIndex(localAddr, ":")
return localAddr[0:idx]
}

4
开发流程.md Normal file
View File

@@ -0,0 +1,4 @@
1.先建model
2.建service
3.建web接口对接
4.建界面,调接口