mirror of
https://github.com/nsnail/NetAdmin.git
synced 2025-07-05 10:08:15 +08:00
feat: ✨ 登录日志独立存储 (#161)
请求日志自动分表 [skip ci] Co-authored-by: tk <fiyne1a@dingtalk.com>
This commit is contained in:
@ -26,4 +26,15 @@ export default {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取缓存项
|
||||
*/
|
||||
getEntry: {
|
||||
url: `${config.API_URL}/api/sys/cache/get.entry`,
|
||||
name: `获取缓存项`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
}
|
106
src/frontend/admin/src/api/sys/loginlog.js
Normal file
106
src/frontend/admin/src/api/sys/loginlog.js
Normal file
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 登录日志服务
|
||||
* @module @/api/sys/login.log
|
||||
*/
|
||||
import config from '@/config'
|
||||
import http from '@/utils/request'
|
||||
export default {
|
||||
/**
|
||||
* 批量删除登录日志
|
||||
*/
|
||||
bulkDelete: {
|
||||
url: `${config.API_URL}/api/sys/login.log/bulk.delete`,
|
||||
name: `批量删除登录日志`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 登录日志计数
|
||||
*/
|
||||
count: {
|
||||
url: `${config.API_URL}/api/sys/login.log/count`,
|
||||
name: `登录日志计数`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建登录日志
|
||||
*/
|
||||
create: {
|
||||
url: `${config.API_URL}/api/sys/login.log/create`,
|
||||
name: `创建登录日志`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除登录日志
|
||||
*/
|
||||
delete: {
|
||||
url: `${config.API_URL}/api/sys/login.log/delete`,
|
||||
name: `删除登录日志`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 登录日志是否存在
|
||||
*/
|
||||
exist: {
|
||||
url: `${config.API_URL}/api/sys/login.log/exist`,
|
||||
name: `登录日志是否存在`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 导出登录日志
|
||||
*/
|
||||
export: {
|
||||
url: `${config.API_URL}/api/sys/login.log/export`,
|
||||
name: `导出登录日志`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取单个登录日志
|
||||
*/
|
||||
get: {
|
||||
url: `${config.API_URL}/api/sys/login.log/get`,
|
||||
name: `获取单个登录日志`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 分页查询登录日志
|
||||
*/
|
||||
pagedQuery: {
|
||||
url: `${config.API_URL}/api/sys/login.log/paged.query`,
|
||||
name: `分页查询登录日志`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询登录日志
|
||||
*/
|
||||
query: {
|
||||
url: `${config.API_URL}/api/sys/login.log/query`,
|
||||
name: `查询登录日志`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
},
|
||||
},
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 请求日志服务
|
||||
* @module @/api/sys/log
|
||||
* @module @/api/sys/request.log
|
||||
*/
|
||||
import config from '@/config'
|
||||
import http from '@/utils/request'
|
||||
@ -9,7 +9,7 @@ export default {
|
||||
* 请求日志计数
|
||||
*/
|
||||
count: {
|
||||
url: `${config.API_URL}/api/sys/log/count`,
|
||||
url: `${config.API_URL}/api/sys/request.log/count`,
|
||||
name: `请求日志计数`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
@ -20,7 +20,7 @@ export default {
|
||||
* 导出请求日志
|
||||
*/
|
||||
export: {
|
||||
url: `${config.API_URL}/api/sys/log/export`,
|
||||
url: `${config.API_URL}/api/sys/request.log/export`,
|
||||
name: `导出请求日志`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
@ -31,7 +31,7 @@ export default {
|
||||
* 获取单个请求日志
|
||||
*/
|
||||
get: {
|
||||
url: `${config.API_URL}/api/sys/log/get`,
|
||||
url: `${config.API_URL}/api/sys/request.log/get`,
|
||||
name: `获取单个请求日志`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
@ -42,7 +42,7 @@ export default {
|
||||
* 获取条形图数据
|
||||
*/
|
||||
getBarChart: {
|
||||
url: `${config.API_URL}/api/sys/log/get.bar.chart`,
|
||||
url: `${config.API_URL}/api/sys/request.log/get.bar.chart`,
|
||||
name: `获取条形图数据`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
@ -53,7 +53,7 @@ export default {
|
||||
* 描述分组饼图数据
|
||||
*/
|
||||
getPieChartByApiSummary: {
|
||||
url: `${config.API_URL}/api/sys/log/get.pie.chart.by.api.summary`,
|
||||
url: `${config.API_URL}/api/sys/request.log/get.pie.chart.by.api.summary`,
|
||||
name: `描述分组饼图数据`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
@ -64,7 +64,7 @@ export default {
|
||||
* 状态码分组饼图数据
|
||||
*/
|
||||
getPieChartByHttpStatusCode: {
|
||||
url: `${config.API_URL}/api/sys/log/get.pie.chart.by.http.status.code`,
|
||||
url: `${config.API_URL}/api/sys/request.log/get.pie.chart.by.http.status.code`,
|
||||
name: `状态码分组饼图数据`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
@ -75,7 +75,7 @@ export default {
|
||||
* 分页查询请求日志
|
||||
*/
|
||||
pagedQuery: {
|
||||
url: `${config.API_URL}/api/sys/log/paged.query`,
|
||||
url: `${config.API_URL}/api/sys/request.log/paged.query`,
|
||||
name: `分页查询请求日志`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
||||
@ -86,7 +86,7 @@ export default {
|
||||
* 查询请求日志
|
||||
*/
|
||||
query: {
|
||||
url: `${config.API_URL}/api/sys/log/query`,
|
||||
url: `${config.API_URL}/api/sys/request.log/query`,
|
||||
name: `查询请求日志`,
|
||||
post: async function (data = {}, config = {}) {
|
||||
return await http.post(this.url, data, config)
|
@ -4,7 +4,7 @@
|
||||
<div
|
||||
:style="{ display: $TOOL.getNestedProperty(row, $attrs.prop) ? 'flex' : 'none' }"
|
||||
@click="click($TOOL.getNestedProperty(row, $attrs.prop))"
|
||||
class="avatar">
|
||||
class="el-table-column-avatar">
|
||||
<el-avatar v-if="$TOOL.getNestedProperty(row, $attrs.nestProp)" :src="getAvatar(row, $attrs.nestProp)" size="small"></el-avatar>
|
||||
<div>
|
||||
<p>{{ $TOOL.getNestedProperty(row, $attrs.nestProp) }}</p>
|
||||
@ -44,18 +44,4 @@ export default {
|
||||
watch: {},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.avatar {
|
||||
div:last-child {
|
||||
line-height: 1.2rem;
|
||||
p:last-child {
|
||||
color: var(--el-color-info-light-3);
|
||||
}
|
||||
}
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
@ -17,7 +17,7 @@ export default {
|
||||
created() {
|
||||
if (this.ip) {
|
||||
this.region = '...'
|
||||
http.get(`http://ip.line92.com/?ip=${this.ip}`).then((x) => {
|
||||
http.get(`http://ip.line92.xyz/?ip=${this.ip}`).then((x) => {
|
||||
this.region = x.region
|
||||
})
|
||||
}
|
||||
|
51
src/frontend/admin/src/components/scTable/fieldFilter.vue
Normal file
51
src/frontend/admin/src/components/scTable/fieldFilter.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<sc-dialog v-model="visible" :title="$t('高级筛选')" :width="800" destroy-on-close>
|
||||
<el-form :model="form" :rules="rules" label-width="10rem" ref="form">
|
||||
<el-form-item :label="$t('字段名')" prop="field">
|
||||
<el-input v-model="form.field" :placeholder="$t('字段名')" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('操作符')" prop="operator">
|
||||
<el-input v-model="form.operator" :placeholder="$t('操作符')" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('字段值')" prop="value">
|
||||
<el-input v-model="form.value" :placeholder="$t('一行一个')" :rows="5" clearable type="textarea" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">{{ $t('取消') }}</el-button>
|
||||
<el-button @click="submit" type="primary">{{ $t('确定') }}</el-button>
|
||||
</template>
|
||||
</sc-dialog>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
visible: false,
|
||||
callback: () => {},
|
||||
rules: {
|
||||
field: [{ required: true, message: this.$t('请输入字段名'), trigger: 'blur' }],
|
||||
operator: [{ required: true, message: this.$t('请输入操作符'), trigger: 'blur' }],
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open(form, callback) {
|
||||
Object.assign(this.form, form)
|
||||
this.visible = true
|
||||
this.callback = callback
|
||||
},
|
||||
async submit() {
|
||||
const valid = await this.$refs.form.validate().catch(() => {})
|
||||
if (!valid) {
|
||||
return false
|
||||
}
|
||||
this.callback(this.form)
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
@ -200,12 +200,14 @@
|
||||
<sc-contextmenu-item v-if="exportApi" :title="$t('导出文件')" command="export" divided icon="el-icon-download"></sc-contextmenu-item>
|
||||
<sc-contextmenu-item :title="$t('重新加载')" command="refresh" icon="el-icon-refresh" suffix="Ctrl+R"></sc-contextmenu-item>
|
||||
</sc-contextmenu>
|
||||
<field-filter ref="fieldFilterDialog"></field-filter>
|
||||
</template>
|
||||
<script>
|
||||
import config from '@/config/table'
|
||||
import columnSetting from './columnSetting'
|
||||
import scContextmenuItem from '@/components/scContextmenu/item.vue'
|
||||
import scContextmenu from '@/components/scContextmenu/index.vue'
|
||||
import fieldFilter from './fieldFilter.vue'
|
||||
import { h } from 'vue'
|
||||
import tool from '@/utils/tool'
|
||||
|
||||
@ -215,6 +217,7 @@ export default {
|
||||
scContextmenu,
|
||||
scContextmenuItem,
|
||||
columnSetting,
|
||||
fieldFilter,
|
||||
},
|
||||
props: {
|
||||
vue: { type: Object },
|
||||
@ -378,29 +381,19 @@ export default {
|
||||
await this.vue.rowDel(this.current.row)
|
||||
return
|
||||
}
|
||||
|
||||
const kv = command.split('^|^')
|
||||
let value
|
||||
try {
|
||||
value = await this.$prompt(this.$t('仅显示 {field} {operator}:', { field: kv[0], operator: kv[1] }), this.$t('高级筛选'), {
|
||||
inputplaceholder: this.$t('一行一个'),
|
||||
inputPattern: /.*/,
|
||||
inputType: 'textarea',
|
||||
inputValue: kv[2],
|
||||
this.$refs.fieldFilterDialog.open({ field: kv[0], operator: kv[1], value: kv[2] }, (data) => {
|
||||
const value = data.value?.split('\n') ?? ['']
|
||||
this.vue.query.dynamicFilter.filters.push({
|
||||
field: data.field,
|
||||
operator: data.operator,
|
||||
value: value.length === 1 ? value[0] : value,
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
value = value.value?.split('\n') ?? ['']
|
||||
this.vue.query.dynamicFilter.filters.push({
|
||||
field: kv[0],
|
||||
operator: kv[1],
|
||||
value: value.length === 1 ? value[0] : value,
|
||||
if (this.onCommand) {
|
||||
this.onCommand(this.vue.query.dynamicFilter.filters)
|
||||
}
|
||||
this.upData()
|
||||
})
|
||||
if (this.onCommand) {
|
||||
this.onCommand(this.vue.query.dynamicFilter.filters)
|
||||
}
|
||||
this.upData()
|
||||
},
|
||||
contextMenuVisibleChange(visible) {
|
||||
if (!visible) {
|
||||
|
@ -486,4 +486,13 @@ export default {
|
||||
'作业趋势(Today)': 'Job trend (Today)',
|
||||
后退一时: 'Back an hour',
|
||||
后退一日: 'Back a day',
|
||||
登录日志: 'Login log',
|
||||
关键词: 'Keyword',
|
||||
数据类型: 'Data type',
|
||||
字段名: 'Field',
|
||||
操作符: 'Operator',
|
||||
字段值: 'Value',
|
||||
一行一个: 'One line per item',
|
||||
请输入字段名: 'Please enter field name',
|
||||
请输入操作符: 'Please enter operator',
|
||||
}
|
@ -483,4 +483,13 @@ export default {
|
||||
'作业趋势(Today)': '作业趋势(Today)',
|
||||
后退一时: '后退一时',
|
||||
后退一日: '后退一日',
|
||||
登录日志: '登录日志',
|
||||
关键词: '关键词',
|
||||
数据类型: '数据类型',
|
||||
字段名: '字段名',
|
||||
操作符: '操作符',
|
||||
字段值: '字段值',
|
||||
一行一个: '一行一个',
|
||||
请输入字段名: '请输入字段名',
|
||||
请输入操作符: '请输入操作符',
|
||||
}
|
@ -36,6 +36,13 @@ const routes = [
|
||||
title: '账号信息',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/profile/logs',
|
||||
component: () => import(/* webpackChunkName: "userRegister" */ '@/views/profile/logs.vue'),
|
||||
meta: {
|
||||
title: '登录日志',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -551,4 +551,20 @@ textarea {
|
||||
.keywords-highlight {
|
||||
background-color: yellow;
|
||||
color: var(--el-color-black);
|
||||
}
|
||||
|
||||
.el-table-column-avatar {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
|
||||
> div:last-child {
|
||||
line-height: 1.2rem;
|
||||
|
||||
> p:last-child {
|
||||
color: var(--el-color-info-light-3);
|
||||
}
|
||||
}
|
||||
}
|
@ -253,12 +253,9 @@ tool.unicodeDecode = function (str) {
|
||||
}
|
||||
// 高亮关键词
|
||||
tool.highLightKeywords = function (str) {
|
||||
console.error(str)
|
||||
var ret = str
|
||||
return str
|
||||
.replace(new RegExp('(Body)', 'gi'), '<span class=keywords-highlight>$1</span>')
|
||||
.replace(new RegExp('(http://cloud_code_adm_api.*?),', 'gi'), '<span class=keywords-highlight>$1</span>')
|
||||
console.error(ret)
|
||||
return ret
|
||||
}
|
||||
// 属性排序
|
||||
tool.sortProperties = function (obj) {
|
||||
|
@ -3,13 +3,13 @@
|
||||
<chart-bar
|
||||
:api="[
|
||||
{
|
||||
file: 'sys_log',
|
||||
file: 'sys_requestlog',
|
||||
name: 'getBarChart',
|
||||
label: '今日',
|
||||
value: ['DateTime.Now.Date', 'DateTime.Now.Date.AddDays(1)'],
|
||||
},
|
||||
{
|
||||
file: 'sys_log',
|
||||
file: 'sys_requestlog',
|
||||
name: 'getBarChart',
|
||||
label: '昨日',
|
||||
value: ['DateTime.Now.Date.AddDays(-1)', 'DateTime.Now.Date'],
|
||||
|
@ -3,13 +3,13 @@
|
||||
<chart-pie
|
||||
:api="[
|
||||
{
|
||||
file: 'sys_log',
|
||||
file: 'sys_requestlog',
|
||||
name: 'getPieChartByApiSummary',
|
||||
value: [tool.dateFormat(new Date(), 'yyyy-MM-dd'), tool.dateFormat(new Date(), 'yyyy-MM-dd')],
|
||||
radius: ['70%', '100%'],
|
||||
},
|
||||
{
|
||||
file: 'sys_log',
|
||||
file: 'sys_requestlog',
|
||||
name: 'getPieChartByHttpStatusCode',
|
||||
value: [tool.dateFormat(new Date(), 'yyyy-MM-dd'), tool.dateFormat(new Date(), 'yyyy-MM-dd')],
|
||||
radius: [0, '30%'],
|
||||
|
@ -29,7 +29,9 @@
|
||||
</el-aside>
|
||||
<el-main class="profile-main">
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" />
|
||||
<keep-alive>
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</el-main>
|
||||
</el-container>
|
||||
@ -63,6 +65,11 @@ export default {
|
||||
title: '我的消息',
|
||||
component: '/profile/message',
|
||||
},
|
||||
{
|
||||
icon: 'el-icon-clock',
|
||||
title: '登录日志',
|
||||
component: '/profile/logs',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -1,39 +1,16 @@
|
||||
<template>
|
||||
<el-card :header="$t('近7天操作记录')" shadow="never">
|
||||
<scTable :data="data" height="auto" hideDo paginationLayout="total, prev, pager, next" ref="table">
|
||||
<sc-table-column :label="$t('序号')" type="index"></sc-table-column>
|
||||
<sc-table-column :label="$t('业务名称')" min-width="240" prop="title"></sc-table-column>
|
||||
<sc-table-column label="IP" prop="ip" width="150"></sc-table-column>
|
||||
<sc-table-column :label="$t('结果')" prop="code" width="150">
|
||||
<el-tag type="success">{{ $t('成功') }}</el-tag>
|
||||
</sc-table-column>
|
||||
<sc-table-column :label="$t('操作时间')" prop="time" width="150"></sc-table-column>
|
||||
</scTable>
|
||||
<el-card :header="$t('登录日志')" shadow="never">
|
||||
<login-log :keywords="$GLOBAL.user.id" :show-filter="false"></login-log>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ScTable from '@/components/scTable/index.vue'
|
||||
import LoginLog from '@/views/sys/log/login/index.vue'
|
||||
|
||||
export default {
|
||||
components: { ScTable },
|
||||
components: { LoginLog },
|
||||
data() {
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
title: '修改用户 lolowan',
|
||||
ip: '211.187.11.18',
|
||||
code: '成功',
|
||||
time: '2022-10-10 08:41:17',
|
||||
},
|
||||
{
|
||||
title: '用户登录',
|
||||
ip: '211.187.11.18',
|
||||
code: '成功',
|
||||
time: '2022-10-10 08:21:51',
|
||||
},
|
||||
],
|
||||
}
|
||||
return {}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
74
src/frontend/admin/src/views/sys/cache/index.vue
vendored
74
src/frontend/admin/src/views/sys/cache/index.vue
vendored
@ -52,14 +52,34 @@
|
||||
</el-row>
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
<el-main class="nopadding">
|
||||
<sc-table :params="query" :query-api="$API.sys_cache.getAllEntries" @row-click="rowClick" ref="table" row-key="key" stripe>
|
||||
<el-table-column :label="$t('键名')" prop="key" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('键值')" prop="data" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('滑动过期')" align="right" prop="sldExpTime" width="200" />
|
||||
<el-table-column :label="$t('绝对过期')" align="right" prop="absExpTime" width="200" />
|
||||
<el-header>
|
||||
<div class="left-panel">
|
||||
<form @keyup.enter="search" @submit.prevent="search" class="right-panel-search">
|
||||
<el-input v-model="this.query.keywords" :placeholder="$t('关键词')" clearable style="width: 25rem" />
|
||||
<el-button-group>
|
||||
<el-button @click="search" icon="el-icon-search" type="primary">{{ $t('查询') }}</el-button>
|
||||
<el-button @click="reset" icon="el-icon-refresh-left">{{ $t('重置') }}</el-button>
|
||||
</el-button-group>
|
||||
</form>
|
||||
</div>
|
||||
<div class="right-panel"></div>
|
||||
</el-header>
|
||||
<el-main v-loading="loading" class="nopadding">
|
||||
<sc-table v-if="tableData.length > 0" :data="tableData" :page-size="100" hide-refresh pagination-layout="total" stripe>
|
||||
<el-table-column :label="$t('键名')" prop="key" />
|
||||
<el-table-column :label="$t('数据类型')" align="center" prop="type" width="100" />
|
||||
<el-table-column :label="$t('过期时间')" align="right" prop="expireTime" width="200" />
|
||||
<na-col-operation
|
||||
:buttons="[
|
||||
{
|
||||
icon: 'el-icon-view',
|
||||
click: rowClick,
|
||||
},
|
||||
]"
|
||||
:vue="this"
|
||||
width="100" />
|
||||
</sc-table>
|
||||
<el-empty v-else></el-empty>
|
||||
</el-main>
|
||||
</el-container>
|
||||
<na-info v-if="dialog.info" ref="info"></na-info>
|
||||
@ -74,14 +94,14 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
dialog: {
|
||||
info: false,
|
||||
},
|
||||
query: {
|
||||
filter: {
|
||||
dbIndex: 1,
|
||||
},
|
||||
keywords: '',
|
||||
},
|
||||
tableData: [],
|
||||
statistics: {
|
||||
keyspaceHits: 0,
|
||||
keyspaceMisses: 0,
|
||||
@ -93,10 +113,32 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset() {
|
||||
this.query.keywords = ''
|
||||
this.search()
|
||||
},
|
||||
search() {
|
||||
this.getData()
|
||||
},
|
||||
async rowClick(row) {
|
||||
this.loading = true
|
||||
const res = await this.$API.sys_cache.getEntry.post({ key: row.key })
|
||||
this.dialog.info = true
|
||||
await this.$nextTick()
|
||||
this.$refs.info.open(this.$TOOL.sortProperties(row), this.$t('缓存详情'))
|
||||
this.$refs.info.open(this.$TOOL.sortProperties(res.data), this.$t('缓存详情'))
|
||||
this.loading = false
|
||||
},
|
||||
async getData() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await this.$API.sys_cache.getAllEntries.post(this.query)
|
||||
if (res.data) {
|
||||
this.tableData = res.data
|
||||
}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
this.loading = false
|
||||
},
|
||||
async cacheStatistics() {
|
||||
try {
|
||||
@ -109,15 +151,9 @@ export default {
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
created() {
|
||||
this.cacheStatistics()
|
||||
},
|
||||
watch: {
|
||||
'query.filter.dbIndex': {
|
||||
handler() {
|
||||
this.$refs.table.upData()
|
||||
},
|
||||
},
|
||||
this.getData()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<el-container>
|
||||
<el-header style="height: auto; padding: 0 1rem">
|
||||
<sc-select-filter
|
||||
v-if="showFilter"
|
||||
:data="[
|
||||
{
|
||||
title: $t('登录结果'),
|
||||
@ -29,7 +30,7 @@
|
||||
},
|
||||
]"
|
||||
:vue="this"
|
||||
@reset="Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))"
|
||||
@reset="onReset"
|
||||
@search="onSearch"
|
||||
dateFormat="YYYY-MM-DD HH:mm:ss"
|
||||
dateType="datetimerange"
|
||||
@ -40,12 +41,12 @@
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<sc-table
|
||||
:context-menus="['id', 'httpStatusCode', 'createdClientIp', 'createdUserAgent', 'createdTime']"
|
||||
:context-menus="['id', 'httpStatusCode', 'loginUserName', 'createdClientIp', 'createdUserAgent', 'createdTime']"
|
||||
:context-opers="['view']"
|
||||
:default-sort="{ prop: 'createdTime', order: 'descending' }"
|
||||
:export-api="$API.sys_log.export"
|
||||
:export-api="$API.sys_loginlog.export"
|
||||
:params="query"
|
||||
:query-api="$API.sys_log.pagedQuery"
|
||||
:query-api="$API.sys_loginlog.pagedQuery"
|
||||
:vue="this"
|
||||
ref="table"
|
||||
remote-filter
|
||||
@ -59,14 +60,14 @@
|
||||
{{ row.httpStatusCode === 200 ? '成功' : '失败' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('登录名')" prop="detail.loginName" width="150" />
|
||||
<el-table-column :label="$t('登录名')" prop="loginUserName" sortable="custom" width="150" />
|
||||
<el-table-column :label="$t('客户端IP')" prop="createdClientIp" show-overflow-tooltip sortable="custom" width="200">
|
||||
<template #default="{ row }">
|
||||
<na-ip :ip="row.createdClientIp"></na-ip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('操作系统')" align="center" prop="detail.os" width="150" />
|
||||
<el-table-column :label="$t('用户代理')" prop="detail.createdUserAgent" show-overflow-tooltip sortable="custom" />
|
||||
<el-table-column :label="$t('操作系统')" align="center" prop="os" width="150" />
|
||||
<el-table-column :label="$t('用户代理')" prop="createdUserAgent" show-overflow-tooltip sortable="custom" />
|
||||
<na-col-operation
|
||||
:buttons="[
|
||||
{
|
||||
@ -91,13 +92,7 @@ export default {
|
||||
naInfo,
|
||||
},
|
||||
computed: {},
|
||||
created() {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'apiPathCrc32',
|
||||
operator: 'eq',
|
||||
value: '1290209789',
|
||||
})
|
||||
},
|
||||
created() {},
|
||||
data() {
|
||||
return {
|
||||
dialog: {
|
||||
@ -106,13 +101,7 @@ export default {
|
||||
loading: false,
|
||||
query: {
|
||||
dynamicFilter: {
|
||||
filters: [
|
||||
{
|
||||
field: 'createdTime',
|
||||
operator: 'dateRange',
|
||||
value: [this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd'), this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')],
|
||||
},
|
||||
],
|
||||
filters: [],
|
||||
},
|
||||
filter: {},
|
||||
keywords: this.keywords,
|
||||
@ -128,15 +117,12 @@ export default {
|
||||
})
|
||||
this.$refs.search.search()
|
||||
},
|
||||
onReset() {
|
||||
if (!this.showFilter) return
|
||||
Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))
|
||||
},
|
||||
//搜索
|
||||
onSearch(form) {
|
||||
if (this.query.dynamicFilter.filters.findIndex((x) => x.field === 'apiPathCrc32') < 0) {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'apiPathCrc32',
|
||||
operator: 'eq',
|
||||
value: '1290209789',
|
||||
})
|
||||
}
|
||||
if (Array.isArray(form.dy.createdTime)) {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'createdTime',
|
||||
@ -165,7 +151,7 @@ export default {
|
||||
async rowClick(row) {
|
||||
this.dialog.info = true
|
||||
await this.$nextTick()
|
||||
const res = await this.$API.sys_log.get.post({
|
||||
const res = await this.$API.sys_loginlog.get.post({
|
||||
id: row.id,
|
||||
})
|
||||
this.$refs.info.open(this.$TOOL.sortProperties(res.data), this.$t('日志详情:{id}', { id: row.id }))
|
||||
@ -180,18 +166,8 @@ export default {
|
||||
type: 'root',
|
||||
})
|
||||
}
|
||||
this.$refs.search.form.dy['apiPathCrc32'] = '1290209789'
|
||||
this.$refs.search.form.dy.createdTime = [
|
||||
`${this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
|
||||
`${this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
|
||||
]
|
||||
this.$refs.search.keeps.push({
|
||||
field: 'createdTime',
|
||||
value: this.$refs.search.form.dy.createdTime,
|
||||
type: 'dy',
|
||||
})
|
||||
},
|
||||
props: ['keywords'],
|
||||
props: { keywords: { type: String }, showFilter: { type: Boolean, default: true } },
|
||||
watch: {},
|
||||
}
|
||||
</script>
|
||||
|
@ -37,16 +37,16 @@
|
||||
},
|
||||
{
|
||||
type: 'cascader',
|
||||
field: ['dy', 'api.id'],
|
||||
field: ['dy', 'apiPathCrc32'],
|
||||
api: $API.sys_api.query,
|
||||
props: { label: 'summary', value: 'id', checkStrictly: true, expandTrigger: 'hover', emitPath: false },
|
||||
props: { label: 'summary', value: 'pathCrc32', checkStrictly: true, expandTrigger: 'hover', emitPath: false },
|
||||
placeholder: $t('请求服务'),
|
||||
style: 'width:20rem',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
field: ['root', 'keywords'],
|
||||
placeholder: $t('日志编号 / 用户 / 客户端IP'),
|
||||
placeholder: $t('日志编号 / 用户编号 / 客户端IP'),
|
||||
style: 'width:25rem',
|
||||
},
|
||||
]"
|
||||
@ -65,10 +65,11 @@
|
||||
:context-menus="['id', 'httpStatusCode', 'apiPathCrc32', 'ownerId', 'httpMethod', 'duration', 'createdClientIp', 'createdTime']"
|
||||
:context-opers="[]"
|
||||
:default-sort="{ prop: 'createdTime', order: 'descending' }"
|
||||
:export-api="$API.sys_log.export"
|
||||
:export-api="$API.sys_requestlog.export"
|
||||
:params="query"
|
||||
:query-api="$API.sys_log.pagedQuery"
|
||||
:query-api="$API.sys_requestlog.pagedQuery"
|
||||
:vue="this"
|
||||
@data-change="dataChange"
|
||||
ref="table"
|
||||
remote-filter
|
||||
remote-sort
|
||||
@ -84,8 +85,12 @@
|
||||
<el-table-column :label="$t('请求服务')" align="center">
|
||||
<el-table-column :label="$t('路径')" prop="apiPathCrc32" show-overflow-tooltip sortable="custom">
|
||||
<template #default="{ row }">
|
||||
<p>{{ row.api.id }}</p>
|
||||
<p>{{ row.api.summary }}</p>
|
||||
<p>
|
||||
{{ apis?.find((x) => x.pathCrc32 === row.apiPathCrc32)?.id }}
|
||||
</p>
|
||||
<p>
|
||||
{{ apis?.find((x) => x.pathCrc32 === row.apiPathCrc32)?.summary }}
|
||||
</p>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('方法')" align="center" prop="httpMethod" sortable="custom" width="100">
|
||||
@ -104,15 +109,29 @@
|
||||
width="90">
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<na-col-user
|
||||
<el-table-column
|
||||
v-auth="'sys/log/operation/user'"
|
||||
:label="$t('用户')"
|
||||
header-align="center"
|
||||
nestProp="owner.userName"
|
||||
nestProp2="owner.id"
|
||||
prop="ownerId"
|
||||
sortable="custom"
|
||||
width="170"></na-col-user>
|
||||
width="170">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.ownerId" :style="{ display: 'flex' }" @click="userClick(row.ownerId)" class="el-table-column-avatar">
|
||||
<el-avatar
|
||||
v-if="owners?.find((x) => x.id === row.ownerId)"
|
||||
:src="
|
||||
owners?.find((x) => x.id === row.ownerId)?.avatar ??
|
||||
$CONFIG.DEFAULT_AVATAR(owners?.find((x) => x.id === row.ownerId)?.userName)
|
||||
"
|
||||
size="small"></el-avatar>
|
||||
<div>
|
||||
<p>{{ owners?.find((x) => x.id === row.ownerId)?.userName }}</p>
|
||||
<p>{{ owners?.find((x) => x.id === row.ownerId)?.id }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('客户端IP')" prop="createdClientIp" show-overflow-tooltip sortable="custom" width="200">
|
||||
<template #default="{ row }">
|
||||
<na-ip :ip="row.createdClientIp"></na-ip>
|
||||
@ -132,14 +151,24 @@
|
||||
</el-container>
|
||||
|
||||
<na-info v-if="dialog.info" ref="info"></na-info>
|
||||
<save-dialog
|
||||
v-if="dialog.save"
|
||||
@closed="dialog.save = null"
|
||||
@mounted="$refs.saveDialog.open(dialog.save)"
|
||||
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
|
||||
ref="saveDialog"></save-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
const saveDialog = defineAsyncComponent(() => import('@/views/sys/user/save.vue'))
|
||||
import naInfo from '@/components/naInfo/index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
naInfo,
|
||||
saveDialog,
|
||||
},
|
||||
computed: {},
|
||||
created() {
|
||||
@ -152,6 +181,8 @@ export default {
|
||||
dialog: {
|
||||
info: false,
|
||||
},
|
||||
owners: [],
|
||||
apis: [],
|
||||
loading: false,
|
||||
query: {
|
||||
dynamicFilter: {
|
||||
@ -159,7 +190,10 @@ export default {
|
||||
{
|
||||
field: 'createdTime',
|
||||
operator: 'dateRange',
|
||||
value: [this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd'), this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')],
|
||||
value: [
|
||||
this.$TOOL.dateFormat(new Date(new Date() - 3600 * 1000), 'yyyy-MM-dd hh:mm:ss'),
|
||||
this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd hh:mm:ss'),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -171,6 +205,38 @@ export default {
|
||||
},
|
||||
inject: ['reload'],
|
||||
methods: {
|
||||
userClick(id) {
|
||||
this.dialog.save = { mode: 'view', row: { id } }
|
||||
},
|
||||
async dataChange(data) {
|
||||
this.owners = []
|
||||
this.apis = []
|
||||
const ownerIds = data.data.rows?.filter((x) => x.ownerId).map((x) => x.ownerId)
|
||||
const apiCrcs = data.data.rows?.map((x) => x.apiPathCrc32)
|
||||
const res = await Promise.all([
|
||||
ownerIds && ownerIds.length > 0
|
||||
? this.$API.sys_user.query.post({
|
||||
dynamicFilter: {
|
||||
field: 'id',
|
||||
operator: 'any',
|
||||
value: ownerIds,
|
||||
},
|
||||
})
|
||||
: new Promise((x) => x({ data: [] })),
|
||||
|
||||
apiCrcs && apiCrcs.length > 0
|
||||
? this.$API.sys_api.query.post({
|
||||
dynamicFilter: {
|
||||
field: 'pathCrc32',
|
||||
operator: 'any',
|
||||
value: apiCrcs,
|
||||
},
|
||||
})
|
||||
: new Promise((x) => x({ data: [] })),
|
||||
])
|
||||
this.owners = res[0].data
|
||||
this.apis = res[1].data
|
||||
},
|
||||
filterChange(data) {
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
this.$refs.search.form.dy[key] = value === 'true' ? true : value === 'false' ? false : value
|
||||
@ -194,11 +260,11 @@ export default {
|
||||
}),
|
||||
})
|
||||
}
|
||||
if (typeof form.dy['api.id'] === 'string' && form.dy['api.id'].trim() !== '') {
|
||||
if (typeof form.dy['apiPathCrc32'] === 'number' && form.dy['apiPathCrc32'] !== 0) {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'api.id',
|
||||
field: 'apiPathCrc32',
|
||||
operator: 'eq',
|
||||
value: form.dy['api.id'],
|
||||
value: form.dy['apiPathCrc32'],
|
||||
})
|
||||
}
|
||||
|
||||
@ -231,8 +297,9 @@ export default {
|
||||
async rowClick(row) {
|
||||
this.dialog.info = true
|
||||
await this.$nextTick()
|
||||
const res = await this.$API.sys_log.get.post({
|
||||
const res = await this.$API.sys_requestlog.get.post({
|
||||
id: row.id,
|
||||
createdTime: row.createdTime,
|
||||
})
|
||||
this.$refs.info.open(this.$TOOL.sortProperties(res.data), this.$t('日志详情:{id}', { id: row.id }))
|
||||
},
|
||||
@ -253,11 +320,12 @@ export default {
|
||||
value: this.ownerId,
|
||||
type: 'dy',
|
||||
})
|
||||
this.$refs.search.form.dy.ownerId = this.ownerId
|
||||
}
|
||||
|
||||
this.$refs.search.form.dy.createdTime = [
|
||||
`${this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
|
||||
`${this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
|
||||
this.$TOOL.dateFormat(new Date(new Date() - 3600 * 1000), 'yyyy-MM-dd hh:mm:ss'),
|
||||
this.$TOOL.dateFormat(new Date(), 'yyyy-MM-dd hh:mm:ss'),
|
||||
]
|
||||
this.$refs.search.keeps.push({
|
||||
field: 'createdTime',
|
||||
|
Reference in New Issue
Block a user