增加系统日志

This commit is contained in:
samwaf
2023-03-29 14:50:10 +08:00
parent 3776cca329
commit c62b9f3624
17 changed files with 765 additions and 1 deletions

View File

@@ -17,6 +17,7 @@ type APIGroup struct {
WafAccountApi
WafAccountLogApi
WafLoginApi
WafSysLogApi
}
var APIGroupAPP = new(APIGroup)
@@ -36,4 +37,6 @@ var (
wafAccountService = waf_service.WafAccountServiceApp
wafAccountLogService = waf_service.WafAccountLogServiceApp
wafTokenInfoService = waf_service.WafTokenInfoServiceApp
wafSysLogService = waf_service.WafSysLogServiceApp
)

36
api/waf_sys_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 WafSysLogApi struct {
}
func (w *WafSysLogApi) GetDetailApi(c *gin.Context) {
var req request.WafSysLogDetailReq
err := c.ShouldBind(&req)
if err == nil {
bean := wafSysLogService.GetDetailApi(req)
response.OkWithDetailed(bean, "获取成功", c)
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafSysLogApi) GetListApi(c *gin.Context) {
var req request.WafSysLogSearchReq
err := c.ShouldBind(&req)
if err == nil {
beans, total, _ := wafSysLogService.GetListApi(req)
response.OkWithDetailed(response.PageResult{
List: beans,
Total: total,
PageIndex: req.PageIndex,
PageSize: req.PageSize,
}, "获取成功", c)
} else {
response.FailWithMessage("解析失败", c)
}
}

View File

@@ -0,0 +1,10 @@
import request from '@/utils/request'
//查询所有系统操作日志列表
export function sys_log_list_api(params) {
return request({
url: 'sys_log/list',
method: 'get',
params: params
})
}

View File

@@ -6,7 +6,7 @@
<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>
<t-input v-model="searchValue" class="search-input" placeholder="请输入你需要搜索的日志" clearable>
<template #suffix-icon>
<search-icon size="20px" />
</template>

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,333 @@
<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 {
sys_log_list_api
} from '@/apis/syslog';
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: '操作类型',
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(){
},
getList(keyword) {
let that = this
this.$request
.get('/sys_log/list', {
params: {
pageSize: that.pagination.pageSize,
pageIndex: that.pagination.current,
op_type: '',
op_content: '',
}
})
.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('/sys_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

@@ -48,6 +48,25 @@ export default [
],
},
{
path: '/sys',
name: 'sys',
component: Layout,
redirect: '/sys',
meta: { title: '系统设置', icon: ViewModuleIcon },
children: [
{
path: 'SysLog',
name: 'SysLog',
component: () => import('@/pages/waf/syslog/index.vue'),
meta: { title: '系统操作日志' },
},
],
},
{
path: '/waf-host',
name: 'wafhost',

View File

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

View File

@@ -0,0 +1,9 @@
package request
import "SamWaf/model/common/request"
type WafSysLogSearchReq struct {
OpType string `json:"op_type" form:"op_type"` //操作类型
OpContent string `json:"op_content" form:"op_content"` //操作内容
request.PageInfo
}

12
model/waf_sys.go Normal file
View File

@@ -0,0 +1,12 @@
package model
import "time"
type WafSysLog struct {
Id string `gorm:"primary_key" json:"id"`
UserCode string `json:"user_code"` //用户码(主要键)
TenantId string `json:"tenant_id"` //租户ID主要键
OpType string `json:"op_type"` //操作类型
OpContent string `json:"op_content"` //操作内容
CreateTime time.Time `json:"create_time"` //创建时间
}

View File

@@ -15,6 +15,7 @@ type ApiGroup struct {
AccountRouter
AccountLogRouter
LoginOutRouter
SysLogRouter
}
type PublicApiGroup struct {
LoginRouter

16
router/waf_sys_log.go Normal file
View File

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

View File

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

View File

@@ -57,6 +57,7 @@ func InitDb() {
logDB.AutoMigrate(&model.StatsIPCityDay{})
logDB.AutoMigrate(&innerbean.WebLog{})
logDB.AutoMigrate(&model.AccountLog{})
logDB.AutoMigrate(&model.WafSysLog{})
global.GWAF_LOCAL_LOG_DB.Callback().Query().Before("gorm:query").Register("tenant_plugin:before_query", before_query)
}

View File

@@ -31,6 +31,7 @@ func InitRouter(r *gin.Engine) {
router.ApiGroupApp.InitAccountRouter(RouterGroup)
router.ApiGroupApp.InitAccountLogRouter(RouterGroup)
router.ApiGroupApp.InitLoginOutRouter(RouterGroup)
router.ApiGroupApp.InitSysLogRouter(RouterGroup)
}
PublicRouterGroup := r.Group("")
router.PublicApiGroupApp.InitLoginRouter(PublicRouterGroup)

View File

@@ -519,6 +519,17 @@ func (waf *WafEngine) Start_WAF() {
waf.HostCode[hosts[i].Code] = hosts[i].Host + ":" + strconv.Itoa(hosts[i].Port)
}
wafSysLog := &model.WafSysLog{
Id: uuid.NewV4().String(),
UserCode: global.GWAF_USER_CODE,
TenantId: global.GWAF_TENANT_ID,
OpType: "信息",
OpContent: "WAF启动",
CreateTime: time.Now(),
}
global.GWAF_LOCAL_LOG_DB.Create(wafSysLog)
for _, v := range waf.ServerOnline {
go func(innruntime innerbean.ServerRunTime) {
@@ -547,6 +558,16 @@ func (waf *WafEngine) Start_WAF() {
if err == http.ErrServerClosed {
zlog.Info("[HTTPServer] https server has been close, cause:[%v]", err)
} else {
//TODO 记录如果https 端口被占用的情况 记录日志 且应该推送websocket
wafSysLog := model.WafSysLog{
Id: uuid.NewV4().String(),
UserCode: global.GWAF_USER_CODE,
TenantId: global.GWAF_TENANT_ID,
OpType: "系统运行错误",
OpContent: "HTTPS端口被占用: " + strconv.Itoa(innruntime.Port) + ",请检查",
CreateTime: time.Time{},
}
global.GWAF_LOCAL_LOG_DB.Create(wafSysLog)
zlog.Error("[HTTPServer] https server start fail, cause:[%v]", err)
}
zlog.Info("server https shutdown")
@@ -571,6 +592,16 @@ func (waf *WafEngine) Start_WAF() {
if err == http.ErrServerClosed {
zlog.Warn("[HTTPServer] http server has been close, cause:[%v]", err)
} else {
//TODO 记录如果http 端口被占用的情况 记录日志 且应该推送websocket
wafSysLog := model.WafSysLog{
Id: uuid.NewV4().String(),
UserCode: global.GWAF_USER_CODE,
TenantId: global.GWAF_TENANT_ID,
OpType: "系统运行错误",
OpContent: "HTTP端口被占用: " + strconv.Itoa(innruntime.Port) + ",请检查",
CreateTime: time.Time{},
}
global.GWAF_LOCAL_LOG_DB.Create(wafSysLog)
zlog.Error("[HTTPServer] http server start fail, cause:[%v]", err)
}
zlog.Info("server http shutdown")
@@ -589,6 +620,15 @@ func (waf *WafEngine) CLoseWAF() {
zlog.Debug("关闭 recover ", e)
}
}()
wafSysLog := &model.WafSysLog{
Id: uuid.NewV4().String(),
UserCode: global.GWAF_USER_CODE,
TenantId: global.GWAF_TENANT_ID,
OpType: "信息",
OpContent: "WAF关闭",
CreateTime: time.Now(),
}
global.GWAF_LOCAL_LOG_DB.Create(wafSysLog)
waf.EngineCurrentStatus = 0
for _, v := range waf.ServerOnline {
if v.Svr != nil {