feat: Add Operate Log
This commit is contained in:
parent
8b52927b4f
commit
4f06cfe1ab
27
ui/src/api/operate-log.ts
Normal file
27
ui/src/api/operate-log.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Result } from '@/request/Result'
|
||||||
|
import { get } from '@/request/index'
|
||||||
|
import type { pageRequest } from '@/api/type/common'
|
||||||
|
import { type Ref } from 'vue'
|
||||||
|
|
||||||
|
const prefix = '/operate_log'
|
||||||
|
/**
|
||||||
|
* 日志分页列表
|
||||||
|
* @param 参数
|
||||||
|
* page {
|
||||||
|
"current_page": "string",
|
||||||
|
"page_size": "string",
|
||||||
|
}
|
||||||
|
* @query 参数
|
||||||
|
param: any
|
||||||
|
*/
|
||||||
|
const getOperateLog: (
|
||||||
|
page: pageRequest,
|
||||||
|
param: any,
|
||||||
|
loading?: Ref<boolean>
|
||||||
|
) => Promise<Result<any>> = (page, param, loading) => {
|
||||||
|
return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getOperateLog
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ import problem from './problem'
|
|||||||
import log from './log'
|
import log from './log'
|
||||||
import applicationWorkflow from './application-workflow'
|
import applicationWorkflow from './application-workflow'
|
||||||
import login from './login'
|
import login from './login'
|
||||||
|
import operateLog from './operate-log'
|
||||||
export default {
|
export default {
|
||||||
notFound,
|
notFound,
|
||||||
application,
|
application,
|
||||||
@ -28,5 +29,6 @@ export default {
|
|||||||
paragraph,
|
paragraph,
|
||||||
problem,
|
problem,
|
||||||
log,
|
log,
|
||||||
login
|
login,
|
||||||
|
operateLog
|
||||||
}
|
}
|
||||||
|
|||||||
30
ui/src/locales/lang/en-US/views/operate-log.ts
Normal file
30
ui/src/locales/lang/en-US/views/operate-log.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export default {
|
||||||
|
title: 'Operate Logs',
|
||||||
|
table: {
|
||||||
|
menu: {
|
||||||
|
label: 'Operate menu'
|
||||||
|
},
|
||||||
|
operate: {
|
||||||
|
label: 'Operate'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
label: 'Operate user'
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
label: 'Status',
|
||||||
|
success: 'Successed',
|
||||||
|
fail: 'Failed',
|
||||||
|
all: 'All'
|
||||||
|
},
|
||||||
|
ip_address: {
|
||||||
|
label: 'IP Address'
|
||||||
|
},
|
||||||
|
opt: {
|
||||||
|
label: 'API Details'
|
||||||
|
},
|
||||||
|
operateTime: {
|
||||||
|
label: 'Operate Time'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: 'Close'
|
||||||
|
}
|
||||||
@ -1,18 +1,19 @@
|
|||||||
import notFound from './404';
|
import notFound from './404'
|
||||||
import application from './application';
|
import application from './application'
|
||||||
import applicationOverview from './application-overview';
|
import applicationOverview from './application-overview'
|
||||||
import dataset from './dataset';
|
import dataset from './dataset'
|
||||||
import system from './system';
|
import system from './system'
|
||||||
import functionLib from './function-lib';
|
import functionLib from './function-lib'
|
||||||
import user from './user';
|
import user from './user'
|
||||||
import team from './team';
|
import team from './team'
|
||||||
import template from './template';
|
import template from './template'
|
||||||
import document from './document';
|
import document from './document'
|
||||||
import paragraph from './paragraph';
|
import paragraph from './paragraph'
|
||||||
import problem from './problem';
|
import problem from './problem'
|
||||||
import log from './log';
|
import log from './log'
|
||||||
import applicationWorkflow from './application-workflow';
|
import applicationWorkflow from './application-workflow'
|
||||||
import login from './login';
|
import login from './login'
|
||||||
|
import operateLog from './operate-log'
|
||||||
export default {
|
export default {
|
||||||
notFound,
|
notFound,
|
||||||
application,
|
application,
|
||||||
@ -28,5 +29,6 @@ export default {
|
|||||||
problem,
|
problem,
|
||||||
log,
|
log,
|
||||||
applicationWorkflow,
|
applicationWorkflow,
|
||||||
login
|
login,
|
||||||
};
|
operateLog
|
||||||
|
}
|
||||||
|
|||||||
30
ui/src/locales/lang/zh-CN/views/operate-log.ts
Normal file
30
ui/src/locales/lang/zh-CN/views/operate-log.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export default {
|
||||||
|
title: '操作日志',
|
||||||
|
table: {
|
||||||
|
menu: {
|
||||||
|
label: '操作菜单'
|
||||||
|
},
|
||||||
|
operate: {
|
||||||
|
label: '操作'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
label: '操作用户'
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
label: '状态',
|
||||||
|
success: '成功',
|
||||||
|
fail: '失败',
|
||||||
|
all: '全部'
|
||||||
|
},
|
||||||
|
ip_address: {
|
||||||
|
label: 'IP地址'
|
||||||
|
},
|
||||||
|
opt: {
|
||||||
|
label: 'API详情'
|
||||||
|
},
|
||||||
|
operateTime: {
|
||||||
|
label: '操作时间'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: '关闭'
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ import problem from './problem'
|
|||||||
import log from './log'
|
import log from './log'
|
||||||
import applicationWorkflow from './application-workflow'
|
import applicationWorkflow from './application-workflow'
|
||||||
import login from './login'
|
import login from './login'
|
||||||
|
import operateLog from './operate-log'
|
||||||
export default {
|
export default {
|
||||||
notFound,
|
notFound,
|
||||||
application,
|
application,
|
||||||
@ -28,5 +29,6 @@ export default {
|
|||||||
paragraph,
|
paragraph,
|
||||||
problem,
|
problem,
|
||||||
log,
|
log,
|
||||||
login
|
login,
|
||||||
|
operateLog
|
||||||
}
|
}
|
||||||
|
|||||||
30
ui/src/locales/lang/zh-Hant/views/operate-log.ts
Normal file
30
ui/src/locales/lang/zh-Hant/views/operate-log.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export default {
|
||||||
|
title: '操作日誌',
|
||||||
|
table: {
|
||||||
|
menu: {
|
||||||
|
label: '操作菜單'
|
||||||
|
},
|
||||||
|
operate: {
|
||||||
|
label: '操作'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
label: '操作用戶'
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
label: '狀態',
|
||||||
|
success: '成功',
|
||||||
|
fail: '失敗',
|
||||||
|
all: '全部'
|
||||||
|
},
|
||||||
|
ip_address: {
|
||||||
|
label: 'IP地址'
|
||||||
|
},
|
||||||
|
opt: {
|
||||||
|
label: 'API詳情'
|
||||||
|
},
|
||||||
|
operateTime: {
|
||||||
|
label: '操作時間'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: '關閉'
|
||||||
|
}
|
||||||
@ -103,6 +103,20 @@ const settingRouter = {
|
|||||||
component: () => import('@/views/email/index.vue')
|
component: () => import('@/views/email/index.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/operate',
|
||||||
|
name: 'operate',
|
||||||
|
meta: {
|
||||||
|
icon: 'app-document',
|
||||||
|
iconActive: 'app-document-active',
|
||||||
|
title: 'views.operateLog.title',
|
||||||
|
activeMenu: '/setting',
|
||||||
|
parentPath: '/setting',
|
||||||
|
parentName: 'setting',
|
||||||
|
permission: new ComplexPermission(['ADMIN'], ['x-pack'], 'AND')
|
||||||
|
},
|
||||||
|
component: () => import('@/views/operate-log/index.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
41
ui/src/views/operate-log/component/DetailDialog.vue
Normal file
41
ui/src/views/operate-log/component/DetailDialog.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.operateLog.table.opt.label')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<el-scrollbar height="400" class="details">
|
||||||
|
<div class="content">{{ details }}</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false">
|
||||||
|
{{ $t('views.operateLog.close') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const details = ref<string>()
|
||||||
|
|
||||||
|
const open = (data: any) => {
|
||||||
|
details.value = JSON.stringify(data.details, null, 4)
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.details {
|
||||||
|
margin: 0 10px 30px;
|
||||||
|
border: 1px #cccccc solid;
|
||||||
|
.content {
|
||||||
|
padding: 10px 20px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
259
ui/src/views/operate-log/index.vue
Normal file
259
ui/src/views/operate-log/index.vue
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
<template>
|
||||||
|
<LayoutContainer :header="$t('views.operateLog.title')">
|
||||||
|
<div class="p-24">
|
||||||
|
<div class="flex-between">
|
||||||
|
<div>
|
||||||
|
<el-select
|
||||||
|
v-model="history_day"
|
||||||
|
class="mr-12"
|
||||||
|
@change="changeDayHandle"
|
||||||
|
style="width: 180px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in dayOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-date-picker
|
||||||
|
v-if="history_day === 'other'"
|
||||||
|
v-model="daterangeValue"
|
||||||
|
type="daterange"
|
||||||
|
:start-placeholder="$t('views.applicationOverview.monitor.startDatePlaceholder')"
|
||||||
|
:end-placeholder="$t('views.applicationOverview.monitor.endDatePlaceholder')"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
@change="changeDayRangeHandle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-between complex-search">
|
||||||
|
<el-select
|
||||||
|
v-model="filter_type"
|
||||||
|
class="complex-search__left"
|
||||||
|
@change="changeFilterHandle"
|
||||||
|
style="width: 120px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in filterOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
|
v-if="filter_type === 'status'"
|
||||||
|
v-model="filter_status"
|
||||||
|
@change="changeStatusHandle"
|
||||||
|
style="width: 220px"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in statusOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input
|
||||||
|
v-else
|
||||||
|
v-model="searchValue"
|
||||||
|
@change="getList"
|
||||||
|
:placeholder="$t('common.search')"
|
||||||
|
prefix-icon="Search"
|
||||||
|
style="width: 220px"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<app-table
|
||||||
|
class="mt-16"
|
||||||
|
:data="tableData"
|
||||||
|
:pagination-config="paginationConfig"
|
||||||
|
@sizeChange="handleSizeChange"
|
||||||
|
@changePage="getList"
|
||||||
|
v-loading="loading"
|
||||||
|
>
|
||||||
|
<el-table-column prop="menu" :label="$t('views.operateLog.table.menu.label')" width="160" />
|
||||||
|
<el-table-column prop="operate" :label="$t('views.operateLog.table.operate.label')" />
|
||||||
|
<el-table-column
|
||||||
|
width="120"
|
||||||
|
prop="user.username"
|
||||||
|
:label="$t('views.operateLog.table.user.label')"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="status"
|
||||||
|
:label="$t('views.operateLog.table.status.label')"
|
||||||
|
width="100"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.status === 200">{{ $t('views.operateLog.table.status.success') }}</span>
|
||||||
|
<span v-else style="color: red">{{ $t('views.operateLog.table.status.fail') }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="ip_address"
|
||||||
|
:label="$t('views.operateLog.table.ip_address.label')"
|
||||||
|
width="160"
|
||||||
|
></el-table-column>
|
||||||
|
<el-table-column :label="$t('views.operateLog.table.operateTime.label')" width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ datetimeFormat(row.create_time) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('common.operation')" width="110" align="left" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="mr-4">
|
||||||
|
<el-button type="primary" text @click.stop="showDetails(row)" class="text-button">
|
||||||
|
{{ $t('views.operateLog.table.opt.label') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</app-table>
|
||||||
|
</div>
|
||||||
|
<DetailDialog ref="DetailDialogRef" />
|
||||||
|
</LayoutContainer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, reactive } from 'vue'
|
||||||
|
import getOperateLog from '@/api/operate-log'
|
||||||
|
import DetailDialog from './component/DetailDialog.vue'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
import { beforeDay, datetimeFormat, nowDate } from '@/utils/time'
|
||||||
|
|
||||||
|
const DetailDialogRef = ref()
|
||||||
|
const loading = ref(false)
|
||||||
|
const paginationConfig = reactive({
|
||||||
|
current_page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
const searchValue = ref('')
|
||||||
|
const tableData = ref<any[]>([])
|
||||||
|
const history_day = ref<number | string>(7)
|
||||||
|
const filter_type = ref<string>('menu')
|
||||||
|
const filter_status = ref<string>('')
|
||||||
|
const daterange = ref({
|
||||||
|
start_time: '',
|
||||||
|
end_time: ''
|
||||||
|
})
|
||||||
|
const daterangeValue = ref('')
|
||||||
|
const dayOptions = [
|
||||||
|
{
|
||||||
|
value: 7,
|
||||||
|
// @ts-ignore
|
||||||
|
label: t('views.applicationOverview.monitor.pastDayOptions.past7Days') // 使用 t 方法来国际化显示文本
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 30,
|
||||||
|
label: t('views.applicationOverview.monitor.pastDayOptions.past30Days')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 90,
|
||||||
|
label: t('views.applicationOverview.monitor.pastDayOptions.past90Days')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 183,
|
||||||
|
label: t('views.applicationOverview.monitor.pastDayOptions.past183Days')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'other',
|
||||||
|
label: t('views.applicationOverview.monitor.pastDayOptions.other')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const filterOptions = [
|
||||||
|
{
|
||||||
|
value: 'menu',
|
||||||
|
label: t('views.operateLog.table.menu.label')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'operate',
|
||||||
|
label: t('views.operateLog.table.operate.label')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'user',
|
||||||
|
label: t('views.operateLog.table.user.label')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'status',
|
||||||
|
label: t('views.operateLog.table.status.label')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'ip_address',
|
||||||
|
label: t('views.operateLog.table.ip_address.label')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const statusOptions = [
|
||||||
|
{
|
||||||
|
value: '200',
|
||||||
|
label: t('views.operateLog.table.status.success')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '500',
|
||||||
|
label: t('views.operateLog.table.status.fail')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
function changeStatusHandle(val: string) {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeFilterHandle(val: string) {
|
||||||
|
filter_type.value = val
|
||||||
|
if (searchValue.value) {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeDayHandle(val: number | string) {
|
||||||
|
if (val !== 'other') {
|
||||||
|
daterange.value.start_time = beforeDay(val)
|
||||||
|
daterange.value.end_time = nowDate
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function changeDayRangeHandle(val: string) {
|
||||||
|
daterange.value.start_time = val[0]
|
||||||
|
daterange.value.end_time = val[1]
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDetails(row: any) {
|
||||||
|
DetailDialogRef.value.open(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSizeChange() {
|
||||||
|
paginationConfig.current_page = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getList() {
|
||||||
|
let obj: any = {
|
||||||
|
start_time: daterange.value.start_time,
|
||||||
|
end_time: daterange.value.end_time
|
||||||
|
}
|
||||||
|
if (searchValue.value && filter_type.value !== 'status') {
|
||||||
|
obj[filter_type.value] = searchValue.value
|
||||||
|
}
|
||||||
|
if (filter_type.value === 'status') {
|
||||||
|
obj['status'] = filter_status.value
|
||||||
|
}
|
||||||
|
return getOperateLog.getOperateLog(paginationConfig, obj, loading).then((res) => {
|
||||||
|
tableData.value = res.data.records
|
||||||
|
paginationConfig.total = res.data.total
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
changeDayHandle(history_day.value)
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.text-button {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue
Block a user