mirror of
https://github.com/continew-org/continew-admin.git
synced 2025-09-10 20:57:14 +08:00
新增:新增系统监控/登录日志功能,优化日志表结构,并新增记录错误信息(非未知异常不记录异常详情,只记录错误信息)
This commit is contained in:
33
continew-admin-ui/src/api/monitor/login-log.ts
Normal file
33
continew-admin-ui/src/api/monitor/login-log.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import axios from 'axios';
|
||||
import qs from 'query-string';
|
||||
|
||||
export interface LoginLogRecord {
|
||||
logId: string;
|
||||
status: number;
|
||||
clientIp: string;
|
||||
location: string;
|
||||
browser: string;
|
||||
errorMsg: string;
|
||||
createUserString: string;
|
||||
createTime: string;
|
||||
}
|
||||
|
||||
export interface LoginLogParams extends Partial<LoginLogRecord> {
|
||||
page: number;
|
||||
size: number;
|
||||
sort: Array<string>;
|
||||
}
|
||||
|
||||
export interface LoginLogListRes {
|
||||
list: LoginLogRecord[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export function queryLoginLogList(params: LoginLogParams) {
|
||||
return axios.get<LoginLogListRes>('/monitor/log/login', {
|
||||
params,
|
||||
paramsSerializer: (obj) => {
|
||||
return qs.stringify(obj);
|
||||
},
|
||||
});
|
||||
}
|
@@ -4,10 +4,11 @@ import qs from 'query-string';
|
||||
export interface OperationLogRecord {
|
||||
logId: string;
|
||||
description: string;
|
||||
status: number,
|
||||
clientIp: string,
|
||||
location: string,
|
||||
browser: string,
|
||||
status: number;
|
||||
clientIp: string;
|
||||
location: string;
|
||||
browser: string;
|
||||
errorMsg: string;
|
||||
createUserString: string;
|
||||
createTime: string;
|
||||
}
|
||||
|
@@ -8,7 +8,8 @@ import localeMonitor from '@/views/dashboard/monitor/locale/en-US';
|
||||
import localeDataAnalysis from '@/views/visualization/data-analysis/locale/en-US';
|
||||
import localeMultiDAnalysis from '@/views/visualization/multi-dimension-data-analysis/locale/en-US';
|
||||
|
||||
import localeLog from '@/views/monitor/log/operation/locale/en-US';
|
||||
import localeOperationLog from '@/views/monitor/log/operation/locale/en-US';
|
||||
import localeLoginLog from '@/views/monitor/log/login/locale/en-US';
|
||||
|
||||
import localeSearchTable from '@/views/list/search-table/locale/en-US';
|
||||
import localeCardList from '@/views/list/card/locale/en-US';
|
||||
@@ -55,7 +56,8 @@ export default {
|
||||
...localeDataAnalysis,
|
||||
...localeMultiDAnalysis,
|
||||
|
||||
...localeLog,
|
||||
...localeOperationLog,
|
||||
...localeLoginLog,
|
||||
|
||||
...localeSearchTable,
|
||||
...localeCardList,
|
||||
|
@@ -8,7 +8,8 @@ import localeMonitor from '@/views/dashboard/monitor/locale/zh-CN';
|
||||
import localeDataAnalysis from '@/views/visualization/data-analysis/locale/zh-CN';
|
||||
import localeMultiDAnalysis from '@/views/visualization/multi-dimension-data-analysis/locale/zh-CN';
|
||||
|
||||
import localeLog from '@/views/monitor/log/operation/locale/zh-CN';
|
||||
import localeOperationLog from '@/views/monitor/log/operation/locale/zh-CN';
|
||||
import localeLoginLog from '@/views/monitor/log/login/locale/zh-CN';
|
||||
|
||||
import localeSearchTable from '@/views/list/search-table/locale/zh-CN';
|
||||
import localeCardList from '@/views/list/card/locale/zh-CN';
|
||||
@@ -55,7 +56,8 @@ export default {
|
||||
...localeDataAnalysis,
|
||||
...localeMultiDAnalysis,
|
||||
|
||||
...localeLog,
|
||||
...localeOperationLog,
|
||||
...localeLoginLog,
|
||||
|
||||
...localeSearchTable,
|
||||
...localeCardList,
|
||||
|
@@ -22,6 +22,16 @@ const Monitor: AppRouteRecordRaw = {
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'log/login',
|
||||
name: 'LoginLog',
|
||||
component: () => import('@/views/monitor/log/login/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.log.login.list',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@@ -2,7 +2,7 @@ import { DEFAULT_LAYOUT } from '../base';
|
||||
import { AppRouteRecordRaw } from '../types';
|
||||
|
||||
const USER: AppRouteRecordRaw = {
|
||||
path: '/system/user',
|
||||
path: '/login/user',
|
||||
name: 'user',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
|
214
continew-admin-ui/src/views/monitor/log/login/index.vue
Normal file
214
continew-admin-ui/src/views/monitor/log/login/index.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['menu.monitor', 'menu.log.login.list']" />
|
||||
<a-card class="general-card" :title="$t('menu.log.login.list')">
|
||||
<a-row style="margin-bottom: 15px">
|
||||
<a-col :span="24">
|
||||
<a-form
|
||||
ref="queryFormRef"
|
||||
:model="queryFormData"
|
||||
layout="inline"
|
||||
>
|
||||
<a-form-item
|
||||
field="status"
|
||||
hide-label
|
||||
>
|
||||
<a-select
|
||||
v-model="queryFormData.status"
|
||||
:options="statusOptions"
|
||||
placeholder="登录状态搜索"
|
||||
allow-clear
|
||||
style="width: 150px;"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="createTime"
|
||||
hide-label
|
||||
>
|
||||
<a-range-picker
|
||||
v-model="queryFormData.createTime"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
show-time
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-button type="primary" @click="toQuery">
|
||||
<template #icon>
|
||||
<icon-search />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="resetQuery">
|
||||
<template #icon>
|
||||
<icon-refresh />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-form>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-table
|
||||
row-key="logId"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:columns="columns"
|
||||
:data="renderData"
|
||||
:bordered="false"
|
||||
:stripe="true"
|
||||
size="large"
|
||||
@page-change="onPageChange"
|
||||
>
|
||||
<template #index="{ rowIndex }">
|
||||
{{ rowIndex + 1 + (pagination.current - 1) * pagination.pageSize }}
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<a-space v-if="record.status === 1">
|
||||
<a-tag color="green">
|
||||
<span class="circle pass"></span>
|
||||
成功
|
||||
</a-tag>
|
||||
</a-space>
|
||||
<a-space v-else>
|
||||
<a-tooltip :content="record.errorMsg">
|
||||
<a-tag color="red" style="cursor: pointer">
|
||||
<span class="circle fail"></span>
|
||||
失败
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #operations>
|
||||
<a-button v-permission="['admin']" type="text" size="small">详情</a-button>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, reactive } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { queryLoginLogList, LoginLogRecord, LoginLogParams } from '@/api/monitor/login-log';
|
||||
import { Pagination } from '@/types/global';
|
||||
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
|
||||
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
|
||||
import { FormInstance } from '@arco-design/web-vue/es/form';
|
||||
|
||||
const { loading, setLoading } = useLoading(true);
|
||||
const queryFormRef = ref<FormInstance>();
|
||||
const renderData = ref<LoginLogRecord[]>([]);
|
||||
|
||||
const queryFormData = ref({
|
||||
status: undefined,
|
||||
createTime: [],
|
||||
});
|
||||
const statusOptions = computed<SelectOptionData[]>(() => [
|
||||
{
|
||||
label: '成功',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '失败',
|
||||
value: 2,
|
||||
},
|
||||
]);
|
||||
|
||||
const basePagination: Pagination = {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
const pagination = reactive({
|
||||
...basePagination,
|
||||
});
|
||||
const columns = computed<TableColumnData[]>(() => [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
slotName: 'index',
|
||||
},
|
||||
{
|
||||
title: '用户昵称',
|
||||
dataIndex: 'createUserString',
|
||||
},
|
||||
{
|
||||
title: '登录行为',
|
||||
dataIndex: 'description',
|
||||
},
|
||||
{
|
||||
title: '登录状态',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
},
|
||||
{
|
||||
title: '登录IP',
|
||||
dataIndex: 'clientIp',
|
||||
},
|
||||
{
|
||||
title: '登录地点',
|
||||
dataIndex: 'location',
|
||||
},
|
||||
{
|
||||
title: '浏览器',
|
||||
dataIndex: 'browser',
|
||||
},
|
||||
{
|
||||
title: '登录时间',
|
||||
dataIndex: 'createTime',
|
||||
},
|
||||
]);
|
||||
const fetchData = async (
|
||||
params: LoginLogParams = { page: 1, size: 10, sort: ['createTime,desc'] }
|
||||
) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await queryLoginLogList(params);
|
||||
renderData.value = data.list;
|
||||
pagination.current = params.page;
|
||||
pagination.total = data.total;
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onPageChange = (current: number) => {
|
||||
fetchData({ page: current, size: pagination.pageSize, sort: ['createTime,desc'] });
|
||||
};
|
||||
|
||||
// 查询
|
||||
const toQuery = () => {
|
||||
fetchData({
|
||||
page: pagination.current,
|
||||
size: pagination.pageSize,
|
||||
sort: ['createTime,desc'],
|
||||
...queryFormData.value,
|
||||
} as unknown as LoginLogParams);
|
||||
};
|
||||
|
||||
// 重置
|
||||
const resetQuery = async () => {
|
||||
await queryFormRef.value?.resetFields();
|
||||
await fetchData();
|
||||
};
|
||||
fetchData();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'LoginLog',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
:deep(.arco-table-th) {
|
||||
&:last-child {
|
||||
.arco-table-th-item-title {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
'menu.log.login.list': 'Login log',
|
||||
};
|
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
'menu.log.login.list': '登录日志',
|
||||
};
|
@@ -81,10 +81,12 @@
|
||||
</a-tag>
|
||||
</a-space>
|
||||
<a-space v-else>
|
||||
<a-tag color="red">
|
||||
<span class="circle fail"></span>
|
||||
失败
|
||||
</a-tag>
|
||||
<a-tooltip :content="record.errorMsg">
|
||||
<a-tag color="red" style="cursor: pointer">
|
||||
<span class="circle fail"></span>
|
||||
失败
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
|
@@ -22,10 +22,12 @@
|
||||
</a-tag>
|
||||
</a-space>
|
||||
<a-space v-else>
|
||||
<a-tag color="red">
|
||||
<span class="circle fail"></span>
|
||||
失败
|
||||
</a-tag>
|
||||
<a-tooltip :content="record.errorMsg">
|
||||
<a-tag color="red" style="cursor: pointer">
|
||||
<span class="circle fail"></span>
|
||||
失败
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #pagination-left>
|
||||
|
Reference in New Issue
Block a user