feat: Add Operate Log

This commit is contained in:
junjun 2025-03-18 18:27:21 +08:00
parent 8b52927b4f
commit 384e8f5f71
10 changed files with 470 additions and 33 deletions

27
ui/src/api/operate-log.ts Normal file
View 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
}

View File

@ -13,6 +13,7 @@ import problem from './problem'
import log from './log'
import applicationWorkflow from './application-workflow'
import login from './login'
import operateLog from './operate-log'
export default {
notFound,
application,
@ -28,5 +29,6 @@ export default {
paragraph,
problem,
log,
login
login,
operateLog
}

View 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'
}

View File

@ -1,32 +1,34 @@
import notFound from './404';
import application from './application';
import applicationOverview from './application-overview';
import dataset from './dataset';
import system from './system';
import functionLib from './function-lib';
import user from './user';
import team from './team';
import template from './template';
import document from './document';
import paragraph from './paragraph';
import problem from './problem';
import log from './log';
import applicationWorkflow from './application-workflow';
import login from './login';
import notFound from './404'
import application from './application'
import applicationOverview from './application-overview'
import dataset from './dataset'
import system from './system'
import functionLib from './function-lib'
import user from './user'
import team from './team'
import template from './template'
import document from './document'
import paragraph from './paragraph'
import problem from './problem'
import log from './log'
import applicationWorkflow from './application-workflow'
import login from './login'
import operateLog from './operate-log'
export default {
notFound,
application,
applicationOverview,
dataset,
system,
functionLib,
user,
team,
template,
document,
paragraph,
problem,
log,
applicationWorkflow,
login
};
notFound,
application,
applicationOverview,
dataset,
system,
functionLib,
user,
team,
template,
document,
paragraph,
problem,
log,
applicationWorkflow,
login,
operateLog
}

View 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: '关闭'
}

View File

@ -13,6 +13,7 @@ import problem from './problem'
import log from './log'
import applicationWorkflow from './application-workflow'
import login from './login'
import operateLog from './operate-log'
export default {
notFound,
application,
@ -28,5 +29,6 @@ export default {
paragraph,
problem,
log,
login
login,
operateLog
}

View 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: '關閉'
}

View File

@ -103,6 +103,20 @@ const settingRouter = {
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')
}
]
}

View 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>

View 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>