Merge pull request #2547 from jumpserver/pr@dev@perf_activity

perf: 增加Activity日志中此资源的任务执行及详情查看
This commit is contained in:
老广 2023-02-13 11:18:26 +08:00 committed by GitHub
commit bf55efeb56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 265 additions and 100 deletions

View File

@ -12,11 +12,11 @@
placement="bottom" placement="bottom"
> >
{{ activity.content }} {{ activity.content }}
<el-link v-if="activity.detail_url" type="primary" @click.native="onClick(activity.detail_url)">
{{ $tc('common.Detail') }}
</el-link>
</el-timeline-item> </el-timeline-item>
</el-timeline> </el-timeline>
<div v-if="activities.length < 1">
{{ this.$t('common.NoContent') }}
</div>
</IBox> </IBox>
</el-col> </el-col>
</el-row> </el-row>
@ -24,9 +24,10 @@
<script> <script>
import IBox from '@/components/IBox' import IBox from '@/components/IBox'
import { openTaskPage } from '@/utils/jms'
export default { export default {
name: 'AccountActivity', name: 'ResourceActivity',
components: { components: {
IBox IBox
}, },
@ -59,6 +60,9 @@ export default {
this.activities.push(res[i]) this.activities.push(res[i])
} }
}) })
},
onClick(taskUrl) {
openTaskPage('', 'celery', taskUrl)
} }
} }
} }

View File

@ -434,6 +434,7 @@
"About": "About", "About": "About",
"PermissionCompany": "Permission company", "PermissionCompany": "Permission company",
"ApproverNumbers": "Approver numbers", "ApproverNumbers": "Approver numbers",
"ConvenientOperate": "Convenient operate",
"Overview": "Overview", "Overview": "Overview",
"Now": "Now", "Now": "Now",
"EnterToContinue": "Press Enter to continue", "EnterToContinue": "Press Enter to continue",

View File

@ -434,6 +434,7 @@
"About": "について", "About": "について",
"PermissionCompany": "授权公司", "PermissionCompany": "授权公司",
"ApproverNumbers": "承認者数", "ApproverNumbers": "承認者数",
"ConvenientOperate": "便利な操作",
"Overview": "概要", "Overview": "概要",
"Now": "今", "Now": "今",
"EnterToContinue": "Enter キーを押して続行します了", "EnterToContinue": "Enter キーを押して続行します了",

View File

@ -431,6 +431,7 @@
"PermissionCompany": "授权公司", "PermissionCompany": "授权公司",
"Filename": "文件名", "Filename": "文件名",
"ApproverNumbers": "审批人数量", "ApproverNumbers": "审批人数量",
"ConvenientOperate": "便捷操作",
"Overview": "概览", "Overview": "概览",
"Now": "现在", "Now": "现在",
"EnterToContinue": "按回车继续输入", "EnterToContinue": "按回车继续输入",

View File

@ -172,7 +172,7 @@ export default {
const activity = { const activity = {
title: this.$t('common.Activity'), title: this.$t('common.Activity'),
name: 'ResourceActivity', name: 'ResourceActivity',
hidden: () => !this.$hasPerm('audits.view_operatelog') hidden: () => !this.$hasPerm('audits.view_activitylog')
} }
return [...this.submenu, activity] return [...this.submenu, activity]
} }

View File

@ -1,8 +1,9 @@
import i18n from '@/i18n/i18n' import i18n from '@/i18n/i18n'
import empty from '@/layout/empty'
export default [ export default [
{ {
path: 'login-log', path: 'login-logs',
name: 'LoginLog', name: 'LoginLog',
component: () => import('@/views/audits/LoginLogList'), component: () => import('@/views/audits/LoginLogList'),
meta: { meta: {
@ -11,14 +12,35 @@ export default [
} }
}, },
{ {
path: 'operate-log', path: 'operate-logs',
name: 'OperateLog', name: '',
component: () => import('@/views/audits/OperateLogList'), component: empty,
meta: {
title: i18n.t('route.OperateLog'),
permissions: ['audits.view_operatelog']
},
children: [
{
path: '',
name: 'OperateLogList',
component: () => import('@/views/audits/OperateLog/OperateLogList'),
meta: { meta: {
title: i18n.t('route.OperateLog'), title: i18n.t('route.OperateLog'),
permissions: ['audits.view_operatelog'] permissions: ['audits.view_operatelog']
} }
}, },
{
path: ':id',
name: 'OperateLogDetail',
component: () => import('@/views/audits/OperateLog/OperateLogDetail/index'),
hidden: true,
meta: {
title: i18n.t('route.OperateLog'),
permissions: ['audits.view_operatelog']
}
}
]
},
{ {
path: 'password-change-log', path: 'password-change-log',
name: 'PasswordChangeLog', name: 'PasswordChangeLog',

View File

@ -2,7 +2,7 @@ import Layout from '@/layout'
import i18n from '@/i18n/i18n' import i18n from '@/i18n/i18n'
import SessionRoutes from './sessions' import SessionRoutes from './sessions'
import LogRoutes from './logs' import LogRoutes from './audits'
import empty from '@/layout/empty' import empty from '@/layout/empty'
import store from '@/store' import store from '@/store'
@ -44,7 +44,7 @@ export default {
children: SessionRoutes children: SessionRoutes
}, },
{ {
path: '/audit/logs', path: '/audit/audits',
component: empty, component: empty,
redirect: '', redirect: '',
name: 'Audits', name: 'Audits',

View File

@ -2,9 +2,12 @@ import store from '@/store'
import { constantRoutes } from '@/router' import { constantRoutes } from '@/router'
import { openWindow } from './common' import { openWindow } from './common'
export function openTaskPage(taskId, taskType) { export function openTaskPage(taskId, taskType, taskUrl) {
taskType = taskType || 'celery' taskType = taskType || 'celery'
openWindow(`/#/ops/${taskType}/task/${taskId}/log/?type=${taskType}`) if (!taskUrl) {
taskUrl = `/#/ops/${taskType}/task/${taskId}/log/?type=${taskType}`
}
openWindow(taskUrl)
} }
export function checkPermission(permsRequired, permsAll) { export function checkPermission(permsRequired, permsAll) {

View File

@ -64,6 +64,9 @@ export default {
datePicker: { datePicker: {
dateStart: dateFrom, dateStart: dateFrom,
dateEnd: dateTo dateEnd: dateTo
},
searchConfig: {
getUrlQuery: true
} }
} }
} }

View File

@ -0,0 +1,69 @@
<template>
<div>
<el-row :gutter="20">
<el-col :md="14" :sm="24">
<AutoDetailCard :url="url" :fields="detailFields" :object="object" />
</el-col>
<el-col :md="10" :sm="24">
<QuickActions
v-if="object.id"
:actions="quickActions"
:title="this.$tc('common.ConvenientOperate')"
type="primary"
/>
</el-col>
</el-row>
<OperateLogDetailDialog
ref="DetailDialog"
/>
</div>
</template>
<script>
import { QuickActions } from '@/components'
import AutoDetailCard from '@/components/DetailCard/auto'
import OperateLogDetailDialog from './DetailDialog'
export default {
name: 'Detail',
components: {
QuickActions,
AutoDetailCard,
OperateLogDetailDialog
},
props: {
object: {
type: Object,
default: () => {}
}
},
data() {
return {
url: `/api/v1/audits/operate-logs/${this.object.id}`,
detailFields: [
'id', 'user', 'remote_addr', 'resource',
'resource_type_display', 'datetime'
],
quickActions: [
{
title: this.$t('audits.ChangeField'),
attrs: {
type: 'primary',
label: this.$t('common.Detail')
},
callbacks: {
click: function() {
this.$refs.DetailDialog.show(this.object.id)
}.bind(this)
}
}
]
}
},
computed: {
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,86 @@
<template>
<Dialog
v-if="logDetailVisible"
:show-confirm="false"
:show-cancel="false"
:title="this.$tc('route.OperateLog')"
:visible.sync="logDetailVisible"
>
<div>
<div v-if="isEmpty()" style="text-align: center">
{{ this.$tc('common.NoContent') }}
</div>
<div v-else>
<el-table
:data="row.diff"
height="500"
style="width: 100%"
>
<el-table-column
:label="this.$tc('audits.ChangeField')"
prop="field"
show-overflow-tooltip
width="100"
/>
<el-table-column
:label="this.$tc('audits.BeforeChange')"
prop="before"
show-overflow-tooltip
/>
<el-table-column
:label="this.$tc('audits.AfterChange')"
prop="after"
show-overflow-tooltip
/>
</el-table>
</div>
</div>
</Dialog>
</template>
<script>
import { Dialog } from '@/components'
export default {
name: 'DetailDialog',
components: {
Dialog
},
props: {},
data() {
return {
row: {
diff: ''
},
logDetailVisible: false
}
},
methods: {
isEmpty() {
const content = this.row.diff
return !content || JSON.stringify(content) === '{}'
},
show(objId) {
this.$axios.get(
`/api/v1/audits/operate-logs/${objId}/?type=action_detail`,
).then(res => {
this.row.diff = res.diff
this.logDetailVisible = true
})
}
}
}
</script>
<style scoped>
.el-tag {
width: 100%;
white-space: normal;
height: auto;
}
.el-table::before {
background-color: inherit;
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<GenericDetailPage
:active-menu.sync="config.activeMenu"
:object.sync="LogDetail"
v-bind="config"
v-on="$listeners"
>
<keep-alive>
<component :is="config.activeMenu" :object="LogDetail" />
</keep-alive>
</GenericDetailPage>
</template>
<script>
import { GenericDetailPage, TabPage } from '@/layout/components'
import Detail from './Detail.vue'
export default {
components: {
GenericDetailPage,
TabPage,
Detail
},
data() {
return {
LogDetail: {},
config: {
activeMenu: 'Detail',
submenu: [
{
title: this.$t('common.BasicInfo'),
name: 'Detail'
}
],
actions: {
hasUpdate: () => false,
hasDelete: () => false
},
hasActivity: false,
getObjectName: (obj) => { return obj.id }
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -5,30 +5,23 @@
:header-actions="headerActions" :header-actions="headerActions"
:table-config="tableConfig" :table-config="tableConfig"
/> />
<Dialog
v-if="logDetailVisible" <OperateLogDetailDialog
:title="this.$tc('route.OperateLog')" ref="DetailDialog"
:visible.sync="logDetailVisible" />
:show-cancel="false"
:show-confirm="false"
>
<OperateLogDetail :row="rowObj" />
</Dialog>
</div> </div>
</template> </template>
<script> <script>
import GenericListPage from '@/layout/components/GenericListPage' import GenericListPage from '@/layout/components/GenericListPage'
import { Dialog } from '@/components'
import { getDaysAgo, getDaysFuture } from '@/utils/common' import { getDaysAgo, getDaysFuture } from '@/utils/common'
import OperateLogDetail from './components/OperateLogDetail' import OperateLogDetailDialog from './OperateLogDetail/DetailDialog'
import { ActionsFormatter } from '@/components/TableFormatters' import { ActionsFormatter } from '@/components/TableFormatters'
export default { export default {
components: { components: {
GenericListPage, GenericListPage,
OperateLogDetail, OperateLogDetailDialog
Dialog
}, },
data() { data() {
const vm = this const vm = this
@ -36,6 +29,7 @@ export default {
const dateFrom = getDaysAgo(7, now).toISOString() const dateFrom = getDaysAgo(7, now).toISOString()
const dateTo = getDaysFuture(1, now).toISOString() const dateTo = getDaysFuture(1, now).toISOString()
return { return {
url: '/api/v1/audits/operate-logs/',
rowObj: { rowObj: {
diff: '' diff: ''
}, },
@ -80,14 +74,8 @@ export default {
type: 'primary', type: 'primary',
callback: ({ row }) => { callback: ({ row }) => {
vm.loading = true vm.loading = true
vm.$axios.get( this.$refs.DetailDialog.show(row.id)
`/api/v1/audits/operate-logs/${row.id}/?type=action_detail`,
).then(res => {
vm.rowObj.diff = res.diff
vm.logDetailVisible = true
}).finally(() => {
vm.loading = false vm.loading = false
})
} }
} }
] ]

View File

@ -1,63 +0,0 @@
<template>
<div>
<div v-if="isEmpty()" style="text-align: center">
{{ this.$tc('common.NoContent') }}
</div>
<div v-else>
<el-table
:data="row.diff"
height="500"
style="width: 100%"
>
<el-table-column
:label="this.$tc('audits.ChangeField')"
prop="field"
show-overflow-tooltip
width="100"
/>
<el-table-column
:label="this.$tc('audits.BeforeChange')"
prop="before"
show-overflow-tooltip
/>
<el-table-column
:label="this.$tc('audits.AfterChange')"
prop="after"
show-overflow-tooltip
/>
</el-table>
</div>
</div>
</template>
<script>
export default {
name: 'OperateLogDetail',
props: {
row: {
type: Object,
default: () => ({})
}
},
methods: {
isEmpty() {
const content = this.row.diff
return !content || JSON.stringify(content) === '{}'
}
}
}
</script>
<style scoped>
.el-tag {
width: 100%;
white-space: normal;
height: auto;
}
.el-table::before {
background-color: inherit;
}
</style>

View File

@ -26,7 +26,7 @@ export default {
this.$t('common.Logging'), this.$t('common.Logging'),
[ [
'LOGIN_LOG_KEEP_DAYS', 'TASK_LOG_KEEP_DAYS', 'OPERATE_LOG_KEEP_DAYS', 'LOGIN_LOG_KEEP_DAYS', 'TASK_LOG_KEEP_DAYS', 'OPERATE_LOG_KEEP_DAYS',
'FTP_LOG_KEEP_DAYS', 'TERMINAL_SESSION_KEEP_DURATION' 'FTP_LOG_KEEP_DAYS', 'TERMINAL_SESSION_KEEP_DURATION', 'ACTIVITY_LOG_KEEP_DAYS'
] ]
], ],
[ [

View File

@ -36,7 +36,8 @@ export default {
name: 'TaskHistory' name: 'TaskHistory'
} }
], ],
hasRightSide: false hasRightSide: false,
hasActivity: false
} }
} }
} }