From 6134e76030d9badd6da4bce43fcb00f6a2ae2575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A9=99=E5=AD=90?= <454313500@qq.com> Date: Wed, 4 Sep 2024 23:31:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E5=9C=A8=E7=BA=BF?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E3=80=81=E7=99=BB=E5=BD=95=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E3=80=81=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 - Yi.Pure.Vue3/mock/asyncRoutes.ts | 20 +- Yi.Pure.Vue3/src/api/monitor/online.ts | 12 + Yi.Pure.Vue3/src/views/login/index.vue | 3 + .../src/views/monitor/logs/login/hook.tsx | 173 +++++++++++++ .../src/views/monitor/logs/login/index.vue | 164 ++++++++++++ .../views/monitor/logs/operation/detail.vue | 79 ++++++ .../src/views/monitor/logs/operation/hook.tsx | 192 ++++++++++++++ .../views/monitor/logs/operation/index.vue | 176 +++++++++++++ .../src/views/monitor/logs/system/detail.vue | 73 ++++++ .../src/views/monitor/logs/system/hook.tsx | 244 ++++++++++++++++++ .../src/views/monitor/logs/system/index.vue | 170 ++++++++++++ .../src/views/monitor/online/hook.tsx | 50 ++-- .../src/views/monitor/online/index.vue | 12 +- 14 files changed, 1326 insertions(+), 44 deletions(-) create mode 100644 Yi.Pure.Vue3/src/api/monitor/online.ts create mode 100644 Yi.Pure.Vue3/src/views/monitor/logs/login/hook.tsx create mode 100644 Yi.Pure.Vue3/src/views/monitor/logs/login/index.vue create mode 100644 Yi.Pure.Vue3/src/views/monitor/logs/operation/detail.vue create mode 100644 Yi.Pure.Vue3/src/views/monitor/logs/operation/hook.tsx create mode 100644 Yi.Pure.Vue3/src/views/monitor/logs/operation/index.vue create mode 100644 Yi.Pure.Vue3/src/views/monitor/logs/system/detail.vue create mode 100644 Yi.Pure.Vue3/src/views/monitor/logs/system/hook.tsx create mode 100644 Yi.Pure.Vue3/src/views/monitor/logs/system/index.vue diff --git a/.gitignore b/.gitignore index 66bc1452..d7c334d3 100644 --- a/.gitignore +++ b/.gitignore @@ -267,8 +267,6 @@ dist .vscode /Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Development.json /Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Production.json -Logs -logs /Yi.Abp.Net8/test/Yi.Abp.Test/appsettings.Development.json /Yi.Abp.Net8/test/Yi.Abp.Test/appsettings.Production.json database_backup diff --git a/Yi.Pure.Vue3/mock/asyncRoutes.ts b/Yi.Pure.Vue3/mock/asyncRoutes.ts index c0525f83..4ea95364 100644 --- a/Yi.Pure.Vue3/mock/asyncRoutes.ts +++ b/Yi.Pure.Vue3/mock/asyncRoutes.ts @@ -101,17 +101,17 @@ const systemMonitorRouter = { title: "menus.pureOperationLog", //roles: ["admin"] } - }, - { - path: "/monitor/system-logs", - component: "monitor/logs/system/index", - name: "SystemLog", - meta: { - icon: "ri:file-search-line", - title: "menus.pureSystemLog", - //roles: ["admin"] - } } + // { + // path: "/monitor/system-logs", + // component: "monitor/logs/system/index", + // name: "SystemLog", + // meta: { + // icon: "ri:file-search-line", + // title: "menus.pureSystemLog", + // //roles: ["admin"] + // } + // } ] }; diff --git a/Yi.Pure.Vue3/src/api/monitor/online.ts b/Yi.Pure.Vue3/src/api/monitor/online.ts new file mode 100644 index 00000000..2b41e6ff --- /dev/null +++ b/Yi.Pure.Vue3/src/api/monitor/online.ts @@ -0,0 +1,12 @@ +import { http } from "@/utils/http"; +import type { Result, ResultPage } from "@/api/result"; + +/** 查询在线用户列表 */ +export const getOnlineList = (query?: object) => { + return http.request("get", "/online", { params: query }); +}; + +/** 强退用户 */ +export const forceLogout = (tokenId?: object) => { + return http.request("delete", `/online/${tokenId}`, {}); +}; diff --git a/Yi.Pure.Vue3/src/views/login/index.vue b/Yi.Pure.Vue3/src/views/login/index.vue index 1a8d419f..3f1039bd 100644 --- a/Yi.Pure.Vue3/src/views/login/index.vue +++ b/Yi.Pure.Vue3/src/views/login/index.vue @@ -101,6 +101,9 @@ const onLogin = async (formEl: FormInstance | undefined) => { message(t("login.pureLoginFail"), { type: "error" }); } }) + .catch(err => { + getCode(); + }) .finally(() => (loading.value = false)); } }); diff --git a/Yi.Pure.Vue3/src/views/monitor/logs/login/hook.tsx b/Yi.Pure.Vue3/src/views/monitor/logs/login/hook.tsx new file mode 100644 index 00000000..1d91287b --- /dev/null +++ b/Yi.Pure.Vue3/src/views/monitor/logs/login/hook.tsx @@ -0,0 +1,173 @@ +import dayjs from "dayjs"; +import { message } from "@/utils/message"; +import { getKeyList } from "@pureadmin/utils"; +import { usePublicHooks } from "@/views/system/hooks"; +import type { PaginationProps } from "@pureadmin/table"; +import { type Ref, reactive, ref, onMounted, toRaw } from "vue"; +import { getLoginLoglist } from "@/api/log/loginLog"; + +export function useRole(tableRef: Ref) { + const form = reactive({ + loginUser: "", + state: "", + creationTime: [], + skipCount: 1, + maxResultCount: 10 + }); + const dataList = ref([]); + const loading = ref(true); + const selectedNum = ref(0); + const { tagStyle } = usePublicHooks(); + + const pagination = reactive({ + total: 0, + pageSize: 10, + currentPage: 1, + background: true + }); + const columns: TableColumnList = [ + { + label: "勾选列", // 如果需要表格多选,此处label必须设置 + type: "selection", + fixed: "left", + reserveSelection: true // 数据刷新后保留选项 + }, + { + label: "序号", + prop: "id", + minWidth: 90 + }, + { + label: "登录用户", + prop: "loginUser", + minWidth: 100 + }, + { + label: "登录 IP", + prop: "loginIp", + minWidth: 140 + }, + { + label: "登录地点", + prop: "loginLocation", + minWidth: 140 + }, + { + label: "操作系统", + prop: "os", + minWidth: 100 + }, + { + label: "浏览器类型", + prop: "browser", + minWidth: 100 + }, + { + label: "登录状态", + prop: "state", + minWidth: 100, + cellRenderer: ({ row, props }) => ( + + {1 === 1 ? "成功" : "失败"} + + ) + }, + { + label: "登录行为", + prop: "logMsg", + minWidth: 100 + }, + { + label: "登录时间", + prop: "creationTime", + minWidth: 180, + formatter: ({ creationTime }) => + dayjs(creationTime).format("YYYY-MM-DD HH:mm:ss") + } + ]; + function handleSizeChange(val: number) { + form.maxResultCount = val; + onSearch(); + } + + function handleCurrentChange(val: number) { + form.skipCount = val; + onSearch(); + } + + /** 当CheckBox选择项发生变化时会触发该事件 */ + function handleSelectionChange(val) { + selectedNum.value = val.length; + // 重置表格高度 + tableRef.value.setAdaptive(); + } + + /** 取消选择 */ + function onSelectionCancel() { + selectedNum.value = 0; + // 用于多选表格,清空用户的选择 + tableRef.value.getTableRef().clearSelection(); + } + + /** 批量删除 */ + function onbatchDel() { + // 返回当前选中的行 + const curSelected = tableRef.value.getTableRef().getSelectionRows(); + // 接下来根据实际业务,通过选中行的某项数据,比如下面的id,调用接口进行批量删除 + message(`已删除序号为 ${getKeyList(curSelected, "id")} 的数据`, { + type: "success" + }); + tableRef.value.getTableRef().clearSelection(); + onSearch(); + } + + /** 清空日志 */ + function clearAll() { + // 根据实际业务,调用接口删除所有日志数据 + message("已删除所有日志数据", { + type: "success" + }); + onSearch(); + } + + async function onSearch() { + loading.value = true; + const { data } = await getLoginLoglist( + toRaw({ + ...form, + startTime: form.creationTime[0], + endTime: form.creationTime[1] + }) + ); + dataList.value = data.items; + pagination.total = data.totalCount; + loading.value = false; + } + + const resetForm = formEl => { + if (!formEl) return; + formEl.resetFields(); + onSearch(); + }; + + onMounted(() => { + onSearch(); + }); + + return { + form, + loading, + columns, + dataList, + pagination, + selectedNum, + onSearch, + clearAll, + resetForm, + onbatchDel, + handleSizeChange, + onSelectionCancel, + handleCurrentChange, + handleSelectionChange + }; +} diff --git a/Yi.Pure.Vue3/src/views/monitor/logs/login/index.vue b/Yi.Pure.Vue3/src/views/monitor/logs/login/index.vue new file mode 100644 index 00000000..a74448bb --- /dev/null +++ b/Yi.Pure.Vue3/src/views/monitor/logs/login/index.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/Yi.Pure.Vue3/src/views/monitor/logs/operation/detail.vue b/Yi.Pure.Vue3/src/views/monitor/logs/operation/detail.vue new file mode 100644 index 00000000..580a7a74 --- /dev/null +++ b/Yi.Pure.Vue3/src/views/monitor/logs/operation/detail.vue @@ -0,0 +1,79 @@ + + + diff --git a/Yi.Pure.Vue3/src/views/monitor/logs/operation/hook.tsx b/Yi.Pure.Vue3/src/views/monitor/logs/operation/hook.tsx new file mode 100644 index 00000000..5bee1c6f --- /dev/null +++ b/Yi.Pure.Vue3/src/views/monitor/logs/operation/hook.tsx @@ -0,0 +1,192 @@ +import dayjs from "dayjs"; +import { message } from "@/utils/message"; +import { getKeyList } from "@pureadmin/utils"; +import { usePublicHooks } from "@/views/system/hooks"; +import type { PaginationProps } from "@pureadmin/table"; +import { type Ref, reactive, ref, onMounted, toRaw } from "vue"; +import { getOperLoglist } from "@/api/log/operLog"; +import { addDialog } from "@/components/ReDialog/index"; +import Detail from "./detail.vue"; + +export function useRole(tableRef: Ref) { + const form = reactive({ + title: "", + state: "", + creationTime: [], + skipCount: 1, + maxResultCount: 10 + }); + const dataList = ref([]); + const loading = ref(true); + const selectedNum = ref(0); + const { tagStyle } = usePublicHooks(); + + const pagination = reactive({ + total: 0, + pageSize: 10, + currentPage: 1, + background: true + }); + const columns: TableColumnList = [ + { + label: "勾选列", // 如果需要表格多选,此处label必须设置 + type: "selection", + fixed: "left", + reserveSelection: true // 数据刷新后保留选项 + }, + { + label: "序号", + prop: "id", + minWidth: 200 + }, + { + label: "操作人员", + prop: "operUser", + minWidth: 100 + }, + { + label: "所属模块", + prop: "title", + minWidth: 140 + }, + { + label: "操作类型", + prop: "operType", + minWidth: 100, + cellRenderer: ({ row, props }) => ( + + {row.operType} + + ) + }, + { + label: "操作 IP", + prop: "operIp", + minWidth: 100 + }, + { + label: "操作地点", + prop: "operLocation", + minWidth: 140 + }, + { + label: "操作状态", + prop: "status", + minWidth: 100, + cellRenderer: ({ row, props }) => ( + + {1 === 1 ? "成功" : "失败"} + + ) + }, + { + label: "操作时间", + prop: "operatingTime", + minWidth: 180, + formatter: ({ operatingTime }) => + dayjs(operatingTime).format("YYYY-MM-DD HH:mm:ss") + }, + { + label: "操作", + fixed: "right", + slot: "operation" + } + ]; + + function handleSizeChange(val: number) { + console.log(`${val} items per page`); + } + + function handleCurrentChange(val: number) { + console.log(`current page: ${val}`); + } + + /** 当CheckBox选择项发生变化时会触发该事件 */ + function handleSelectionChange(val) { + selectedNum.value = val.length; + // 重置表格高度 + tableRef.value.setAdaptive(); + } + + /** 取消选择 */ + function onSelectionCancel() { + selectedNum.value = 0; + // 用于多选表格,清空用户的选择 + tableRef.value.getTableRef().clearSelection(); + } + + /** 批量删除 */ + function onbatchDel() { + // 返回当前选中的行 + const curSelected = tableRef.value.getTableRef().getSelectionRows(); + // 接下来根据实际业务,通过选中行的某项数据,比如下面的id,调用接口进行批量删除 + message(`已删除序号为 ${getKeyList(curSelected, "id")} 的数据`, { + type: "success" + }); + tableRef.value.getTableRef().clearSelection(); + onSearch(); + } + + /** 清空日志 */ + function clearAll() { + // 根据实际业务,调用接口删除所有日志数据 + message("已删除所有日志数据", { + type: "success" + }); + onSearch(); + } + + async function onSearch() { + loading.value = true; + const { data } = await getOperLoglist( + toRaw({ + ...form, + startTime: form.creationTime[0], + endTime: form.creationTime[1] + }) + ); + dataList.value = data.items; + pagination.total = data.totalCount; + loading.value = false; + } + + const resetForm = formEl => { + if (!formEl) return; + formEl.resetFields(); + onSearch(); + }; + + function onDetail(row) { + addDialog({ + title: "操作日志详情", + fullscreen: true, + hideFooter: true, + contentRenderer: () => Detail, + props: { + data: [row] + } + }); + } + + onMounted(() => { + onSearch(); + }); + + return { + form, + loading, + columns, + dataList, + pagination, + selectedNum, + onDetail, + onSearch, + clearAll, + resetForm, + onbatchDel, + handleSizeChange, + onSelectionCancel, + handleCurrentChange, + handleSelectionChange + }; +} diff --git a/Yi.Pure.Vue3/src/views/monitor/logs/operation/index.vue b/Yi.Pure.Vue3/src/views/monitor/logs/operation/index.vue new file mode 100644 index 00000000..576ed8bb --- /dev/null +++ b/Yi.Pure.Vue3/src/views/monitor/logs/operation/index.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/Yi.Pure.Vue3/src/views/monitor/logs/system/detail.vue b/Yi.Pure.Vue3/src/views/monitor/logs/system/detail.vue new file mode 100644 index 00000000..052f3311 --- /dev/null +++ b/Yi.Pure.Vue3/src/views/monitor/logs/system/detail.vue @@ -0,0 +1,73 @@ + + + diff --git a/Yi.Pure.Vue3/src/views/monitor/logs/system/hook.tsx b/Yi.Pure.Vue3/src/views/monitor/logs/system/hook.tsx new file mode 100644 index 00000000..c5b59e4f --- /dev/null +++ b/Yi.Pure.Vue3/src/views/monitor/logs/system/hook.tsx @@ -0,0 +1,244 @@ +import dayjs from "dayjs"; +import Detail from "../detail.vue"; +import { message } from "@/utils/message"; +import { addDialog } from "@/components/ReDialog"; +import type { PaginationProps } from "@pureadmin/table"; +import { type Ref, reactive, ref, onMounted, toRaw } from "vue"; +import { getKeyList, useCopyToClipboard } from "@pureadmin/utils"; +import { getSystemLogsList, getSystemLogsDetail } from "@/api/system"; +import Info from "@iconify-icons/ri/question-line"; + +export function useRole(tableRef: Ref) { + const form = reactive({ + module: "", + requestTime: "" + }); + const dataList = ref([]); + const loading = ref(true); + const selectedNum = ref(0); + const { copied, update } = useCopyToClipboard(); + + const pagination = reactive({ + total: 0, + pageSize: 10, + currentPage: 1, + background: true + }); + + // const getLevelType = (type, text = false) => { + // switch (type) { + // case 0: + // return text ? "debug" : "primary"; + // case 1: + // return text ? "info" : "success"; + // case 2: + // return text ? "warn" : "info"; + // case 3: + // return text ? "error" : "warning"; + // case 4: + // return text ? "fatal" : "danger"; + // } + // }; + + const columns: TableColumnList = [ + { + label: "勾选列", // 如果需要表格多选,此处label必须设置 + type: "selection", + fixed: "left", + reserveSelection: true // 数据刷新后保留选项 + }, + { + label: "ID", + prop: "id", + minWidth: 90 + }, + { + label: "所属模块", + prop: "module", + minWidth: 100 + }, + { + headerRenderer: () => ( + + 请求接口 + + + ), + prop: "url", + minWidth: 140 + }, + { + label: "请求方法", + prop: "method", + minWidth: 140 + }, + { + label: "IP 地址", + prop: "ip", + minWidth: 100 + }, + { + label: "地点", + prop: "address", + minWidth: 140 + }, + { + label: "操作系统", + prop: "system", + minWidth: 100 + }, + { + label: "浏览器类型", + prop: "browser", + minWidth: 100 + }, + // { + // label: "级别", + // prop: "level", + // minWidth: 90, + // cellRenderer: ({ row, props }) => ( + // + // {getLevelType(row.level, true)} + // + // ) + // }, + { + label: "请求耗时", + prop: "takesTime", + minWidth: 100, + cellRenderer: ({ row, props }) => ( + + {row.takesTime} ms + + ) + }, + { + label: "请求时间", + prop: "requestTime", + minWidth: 180, + formatter: ({ requestTime }) => + dayjs(requestTime).format("YYYY-MM-DD HH:mm:ss") + }, + { + label: "操作", + fixed: "right", + slot: "operation" + } + ]; + + function handleSizeChange(val: number) { + console.log(`${val} items per page`); + } + + function handleCurrentChange(val: number) { + console.log(`current page: ${val}`); + } + + /** 当CheckBox选择项发生变化时会触发该事件 */ + function handleSelectionChange(val) { + selectedNum.value = val.length; + // 重置表格高度 + tableRef.value.setAdaptive(); + } + + /** 取消选择 */ + function onSelectionCancel() { + selectedNum.value = 0; + // 用于多选表格,清空用户的选择 + tableRef.value.getTableRef().clearSelection(); + } + + /** 拷贝请求接口,表格单元格被双击时触发 */ + function handleCellDblclick({ url }, { property }) { + if (property !== "url") return; + update(url); + copied.value + ? message(`${url} 已拷贝`, { type: "success" }) + : message("拷贝失败", { type: "warning" }); + } + + /** 批量删除 */ + function onbatchDel() { + // 返回当前选中的行 + const curSelected = tableRef.value.getTableRef().getSelectionRows(); + // 接下来根据实际业务,通过选中行的某项数据,比如下面的id,调用接口进行批量删除 + message(`已删除序号为 ${getKeyList(curSelected, "id")} 的数据`, { + type: "success" + }); + tableRef.value.getTableRef().clearSelection(); + onSearch(); + } + + /** 清空日志 */ + function clearAll() { + // 根据实际业务,调用接口删除所有日志数据 + message("已删除所有日志数据", { + type: "success" + }); + onSearch(); + } + + function onDetail(row) { + getSystemLogsDetail({ id: row.id }).then(res => { + addDialog({ + title: "系统日志详情", + fullscreen: true, + hideFooter: true, + contentRenderer: () => Detail, + props: { + data: [res] + } + }); + }); + } + + async function onSearch() { + loading.value = true; + const { data } = await getSystemLogsList(toRaw(form)); + dataList.value = data.items; + pagination.total = data.totalCount; + + setTimeout(() => { + loading.value = false; + }, 500); + } + + const resetForm = formEl => { + if (!formEl) return; + formEl.resetFields(); + onSearch(); + }; + + onMounted(() => { + onSearch(); + }); + + return { + form, + loading, + columns, + dataList, + pagination, + selectedNum, + onSearch, + onDetail, + clearAll, + resetForm, + onbatchDel, + handleSizeChange, + onSelectionCancel, + handleCellDblclick, + handleCurrentChange, + handleSelectionChange + }; +} diff --git a/Yi.Pure.Vue3/src/views/monitor/logs/system/index.vue b/Yi.Pure.Vue3/src/views/monitor/logs/system/index.vue new file mode 100644 index 00000000..20160b02 --- /dev/null +++ b/Yi.Pure.Vue3/src/views/monitor/logs/system/index.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/Yi.Pure.Vue3/src/views/monitor/online/hook.tsx b/Yi.Pure.Vue3/src/views/monitor/online/hook.tsx index 65b5936e..b967e61a 100644 --- a/Yi.Pure.Vue3/src/views/monitor/online/hook.tsx +++ b/Yi.Pure.Vue3/src/views/monitor/online/hook.tsx @@ -3,10 +3,13 @@ import { message } from "@/utils/message"; import { getOnlineLogsList } from "@/api/system"; import { reactive, ref, onMounted, toRaw } from "vue"; import type { PaginationProps } from "@pureadmin/table"; +import {forceLogout, getOnlineList} from "@/api/monitor/online"; export function useRole() { const form = reactive({ - username: "" + userName: "", + skipCount: 1, + maxResultCount: 10 }); const dataList = ref([]); const loading = ref(true); @@ -19,27 +22,27 @@ export function useRole() { const columns: TableColumnList = [ { label: "序号", - prop: "id", - minWidth: 60 + prop: "connnectionId", + minWidth: 200 }, { label: "用户名", - prop: "username", + prop: "userName", minWidth: 100 }, { label: "登录 IP", - prop: "ip", + prop: "ipaddr", minWidth: 140 }, { label: "登录地点", - prop: "address", + prop: "loginLocation", minWidth: 140 }, { label: "操作系统", - prop: "system", + prop: "os", minWidth: 100 }, { @@ -49,10 +52,10 @@ export function useRole() { }, { label: "登录时间", - prop: "loginTime", + prop: "creationTime", minWidth: 180, - formatter: ({ loginTime }) => - dayjs(loginTime).format("YYYY-MM-DD HH:mm:ss") + formatter: ({ creationTime }) => + dayjs(creationTime).format("YYYY-MM-DD HH:mm:ss") }, { label: "操作", @@ -62,33 +65,32 @@ export function useRole() { ]; function handleSizeChange(val: number) { - console.log(`${val} items per page`); + form.maxResultCount = val; + onSearch(); } function handleCurrentChange(val: number) { - console.log(`current page: ${val}`); + form.skipCount = val; + onSearch(); } + /** 当CheckBox选择项发生变化时会触发该事件 */ function handleSelectionChange(val) { - console.log("handleSelectionChange", val); + console.log(val); } - function handleOffline(row) { - message(`${row.username}已被强制下线`, { type: "success" }); + async function handleOffline(row) { + await forceLogout(row.connnectionId); + message(`${row.userName}已被强制下线`, { type: "success" }); onSearch(); } async function onSearch() { loading.value = true; - const { data } = await getOnlineLogsList(toRaw(form)); - dataList.value = data.list; - pagination.total = data.total; - pagination.pageSize = data.pageSize; - pagination.currentPage = data.currentPage; - - setTimeout(() => { - loading.value = false; - }, 500); + const { data } = await getOnlineList(toRaw(form)); + dataList.value = data.items; + pagination.total = data.totalCount; + loading.value = false; } const resetForm = formEl => { diff --git a/Yi.Pure.Vue3/src/views/monitor/online/index.vue b/Yi.Pure.Vue3/src/views/monitor/online/index.vue index fb854a79..172c10b6 100644 --- a/Yi.Pure.Vue3/src/views/monitor/online/index.vue +++ b/Yi.Pure.Vue3/src/views/monitor/online/index.vue @@ -35,9 +35,9 @@ const { :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto" > - + - +