| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-tabs v-model="activeTab" type="border-card"> |
| | | <!-- ==================== Tab1: 审计追踪 ==================== --> |
| | | <el-tab-pane label="审计追踪" name="audit"> |
| | | <el-alert type="info" :closable="false" style="margin-bottom: 12px"> |
| | | <template #title> |
| | | è®°å½æææ°æ®çå¢å æ¹ä¸å
³é®ä¸å¡æä½ï¼æ ·ååºåºãå®éªèµæºåé
ãåè¦å¤ççï¼ï¼ |
| | | å
嫿ä½äººãIPãåæ®µåæ´ååå¼ï¼æ»¡è¶³åè§è¿½æº¯è¦æ±ã |
| | | </template> |
| | | </el-alert> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">模åï¼</span> |
| | | <el-input v-model="auditSearch.module" style="width: 140px" placeholder="å¦ æ ·å管ç" clearable @change="loadAudit" /> |
| | | <span class="search_title" style="margin-left: 14px">å¨ä½ï¼</span> |
| | | <el-select v-model="auditSearch.action" style="width: 150px" placeholder="å
¨é¨" clearable @change="loadAudit"> |
| | | <el-option label="æ°å¢ create" value="create" /> |
| | | <el-option label="ä¿®æ¹ update" value="update" /> |
| | | <el-option label="åºåº stock_out" value="stock_out" /> |
| | | <el-option label="å
¥åº stock_in" value="stock_in" /> |
| | | <el-option label="åé
èµæº assign_resource" value="assign_resource" /> |
| | | <el-option label="å¤çåè¦ handle_alert" value="handle_alert" /> |
| | | </el-select> |
| | | <span class="search_title" style="margin-left: 14px">æä½äººï¼</span> |
| | | <el-input v-model="auditSearch.operator" style="width: 130px" placeholder="请è¾å
¥" clearable @change="loadAudit" /> |
| | | <span class="search_title" style="margin-left: 14px">ç»æï¼</span> |
| | | <el-select v-model="auditSearch.execResult" style="width: 110px" placeholder="å
¨é¨" clearable @change="loadAudit"> |
| | | <el-option label="æå" value="success" /> |
| | | <el-option label="失败" value="fail" /> |
| | | </el-select> |
| | | <el-button type="primary" style="margin-left: 10px" @click="loadAudit">æç´¢</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button @click="loadAudit">å·æ°</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable rowKey="id" :column="auditColumns" :tableData="auditData" :page="auditPage" |
| | | :tableLoading="auditLoading" @pagination="paginationAudit" /> |
| | | </el-tab-pane> |
| | | |
| | | <!-- ==================== Tab2: è®¿é®æ§å¶ ==================== --> |
| | | <el-tab-pane label="è®¿é®æ§å¶" name="access"> |
| | | <el-alert type="success" :closable="false" style="margin-bottom: 16px"> |
| | | <template #title> |
| | | åºäº RuoYi æéä½ç³»ï¼ç¨æ· â è§è² â èå/æé®æéãæææ¥å£é½æ @PreAuthorize æ ¡éªï¼ |
| | | æªææè®¿é®è¿å 401/403ï¼ç»å½å¯ç ç¨ BCrypt å å¯åå¨ï¼ä¸è½ææã |
| | | </template> |
| | | </el-alert> |
| | | |
| | | <el-row :gutter="16"> |
| | | <el-col :span="8"> |
| | | <el-card shadow="hover"> |
| | | <template #header> |
| | | <div style="display: flex; justify-content: space-between; align-items: center"> |
| | | <span style="font-weight: 600">ç¨æ·ç®¡ç</span> |
| | | <el-button link type="primary" @click="goSystem('user')">åå¾ â</el-button> |
| | | </div> |
| | | </template> |
| | | <div style="color: #606266; font-size: 13px; line-height: 1.8"> |
| | | ç»´æ¤ç³»ç»ä½¿ç¨è
è´¦å·ï¼åé
è§è²ä¸é¨é¨ã<br> |
| | | å½åç¨æ·ï¼<b>{{ stat.userCount ?? '-' }}</b> 人 |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-card shadow="hover"> |
| | | <template #header> |
| | | <div style="display: flex; justify-content: space-between; align-items: center"> |
| | | <span style="font-weight: 600">è§è²ç®¡ç</span> |
| | | <el-button link type="primary" @click="goSystem('role')">åå¾ â</el-button> |
| | | </div> |
| | | </template> |
| | | <div style="color: #606266; font-size: 13px; line-height: 1.8"> |
| | | æå²ä½ååè§è²ï¼é
ç½®å¯è®¿é®çèååæ°æ®èå´ã<br> |
| | | å½åè§è²ï¼<b>{{ stat.roleCount ?? '-' }}</b> 个 |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-card shadow="hover"> |
| | | <template #header> |
| | | <div style="display: flex; justify-content: space-between; align-items: center"> |
| | | <span style="font-weight: 600">èåæé</span> |
| | | <el-button link type="primary" @click="goSystem('menu')">åå¾ â</el-button> |
| | | </div> |
| | | </template> |
| | | <div style="color: #606266; font-size: 13px; line-height: 1.8"> |
| | | æ§å¶æ¯ä¸ªè§è²è½çå°åªäºèåãè½ç¹åªäºæé®ã<br> |
| | | å½åèåï¼<b>{{ stat.menuCount ?? '-' }}</b> 项 |
| | | </div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-divider content-position="left">ç»å½ä¸æä½å®¡è®¡</el-divider> |
| | | <el-row :gutter="16"> |
| | | <el-col :span="8"> |
| | | <el-card shadow="hover"> |
| | | <template #header> |
| | | <div style="display: flex; justify-content: space-between; align-items: center"> |
| | | <span style="font-weight: 600">ç»å½æ¥å¿</span> |
| | | <el-button link type="primary" @click="goMonitor('logininfor')">åå¾ â</el-button> |
| | | </div> |
| | | </template> |
| | | <div style="color: #606266; font-size: 13px">è®°å½æ¯æ¬¡ç»å½ç IPãå°ç¹ãæµè§å¨ãæå/失败</div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-card shadow="hover"> |
| | | <template #header> |
| | | <div style="display: flex; justify-content: space-between; align-items: center"> |
| | | <span style="font-weight: 600">æä½æ¥å¿</span> |
| | | <el-button link type="primary" @click="goMonitor('operlog')">åå¾ â</el-button> |
| | | </div> |
| | | </template> |
| | | <div style="color: #606266; font-size: 13px">æ¡æ¶çº§æä½æ¥å¿ï¼ææå¸¦ @Log æ³¨è§£çæ¥å£ï¼</div> |
| | | </el-card> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-card shadow="hover"> |
| | | <template #header> |
| | | <div style="display: flex; justify-content: space-between; align-items: center"> |
| | | <span style="font-weight: 600">å¨çº¿ç¨æ·</span> |
| | | <el-button link type="primary" @click="goMonitor('online')">åå¾ â</el-button> |
| | | </div> |
| | | </template> |
| | | <div style="color: #606266; font-size: 13px">å½åç»å½ä¼è¯ï¼å¯å¼ºå¶ä¸çº¿</div> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-divider content-position="left">å®å
¨æªæ½è¯´æ</el-divider> |
| | | <el-table :data="securityMeasures" border size="small"> |
| | | <el-table-column prop="item" label="æªæ½" width="180" /> |
| | | <el-table-column prop="impl" label="å®ç°æ¹å¼" /> |
| | | <el-table-column prop="status" label="ç¶æ" width="100" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.status === '已宿½' ? 'success' : 'info'" size="small">{{ row.status }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-tab-pane> |
| | | |
| | | <!-- ==================== Tab3: å®å
¨çç¥ ==================== --> |
| | | <el-tab-pane label="å®å
¨çç¥" name="policy"> |
| | | <el-alert type="warning" :closable="false" style="margin-bottom: 12px"> |
| | | <template #title> |
| | | 以ä¸åæ°æ§å¶å®å
¨ç¸å
³è¡ä¸ºï¼ä¿®æ¹åç«å³çæï¼å®¡è®¡ä¸åæ°æå¡ææ¬å°ç¼åï¼æ¹å®å·æ°é¡µé¢å³å¯ï¼ã |
| | | </template> |
| | | </el-alert> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">åæ°åï¼</span> |
| | | <el-input v-model="secSearch.settingName" style="width: 180px" placeholder="请è¾å
¥" clearable @change="loadSecurity" /> |
| | | <el-button type="primary" style="margin-left: 10px" @click="loadSecurity">æç´¢</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button @click="loadSecurity">å·æ°</el-button> |
| | | </div> |
| | | </div> |
| | | <el-table :data="secData" :loading="secLoading" border size="small"> |
| | | <el-table-column prop="settingKey" label="åæ°é®" width="230" /> |
| | | <el-table-column prop="settingName" label="åæ°å" width="160" /> |
| | | <el-table-column prop="settingValue" label="å½åå¼" width="140" /> |
| | | <el-table-column prop="defaultValue" label="é»è®¤å¼" width="110" /> |
| | | <el-table-column prop="description" label="说æ" show-overflow-tooltip /> |
| | | <el-table-column label="æä½" width="90" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button link type="primary" @click="openEdit(row)">ä¿®æ¹</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <!-- ===== åæ°ç¼è¾å¼¹çª ===== --> |
| | | <el-dialog v-model="editVisible" title="ä¿®æ¹å®å
¨çç¥" width="560px"> |
| | | <el-form :model="editForm" label-position="top"> |
| | | <el-form-item label="åæ°é®"><el-input v-model="editForm.settingKey" disabled /></el-form-item> |
| | | <el-form-item :label="editForm.settingName"> |
| | | <el-input v-model="editForm.settingValue" placeholder="请è¾å
¥" /> |
| | | <div style="color: #909399; font-size: 12px; margin-top: 4px">{{ editForm.description }}</div> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="editVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" @click="submitEdit">ä¿å</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { listAuditLog } from "@/api/lims/auditLog"; |
| | | import { listSetting, updateSetting } from "@/api/lims/setting"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const router = useRouter(); |
| | | const activeTab = ref("audit"); |
| | | |
| | | /* ==================== 审计追踪 ==================== */ |
| | | const auditSearch = ref({}); |
| | | const auditData = ref([]); |
| | | const auditLoading = ref(false); |
| | | const auditPage = reactive({ pageNum: 1, pageSize: 20, total: 0 }); |
| | | |
| | | const auditColumns = ref([ |
| | | { label: "æ¶é´", prop: "operateTime", width: 160 }, |
| | | { label: "模å", prop: "module", width: 110 }, |
| | | { label: "å¨ä½", prop: "action", width: 140 }, |
| | | { label: "ä¸å¡ç¼å·", prop: "bizCode", width: 130 }, |
| | | { label: "说æ", prop: "description", showOverflowTooltip: true }, |
| | | { label: "åæ®µ", prop: "fieldName", width: 100 }, |
| | | { label: "æ§å¼", prop: "oldValue", width: 100 }, |
| | | { label: "æ°å¼", prop: "newValue", width: 100 }, |
| | | { label: "æä½äºº", prop: "operator", width: 100 }, |
| | | { label: "IP", prop: "operatorIp", width: 130 }, |
| | | { label: "ç»æ", prop: "execResult", width: 80 }, |
| | | ]); |
| | | |
| | | const loadAudit = () => { |
| | | auditLoading.value = true; |
| | | listAuditLog({ ...auditSearch.value, ...auditPage }).then((res) => { |
| | | auditLoading.value = false; |
| | | auditData.value = res.data.records || []; |
| | | auditPage.total = res.data.total || 0; |
| | | }).catch(() => (auditLoading.value = false)); |
| | | }; |
| | | const paginationAudit = (p) => { auditPage.pageNum = p.pageNum; auditPage.pageSize = p.pageSize; loadAudit(); }; |
| | | |
| | | /* ==================== è®¿é®æ§å¶ ==================== */ |
| | | const stat = ref({}); |
| | | const securityMeasures = ref([ |
| | | { item: "å¯ç åå¨", impl: "BCrypt å çåå¸ï¼ä¸å¯éï¼ï¼æ°æ®åºä¸ä¸è½ææ", status: "已宿½" }, |
| | | { item: "æ¥å£é´æ", impl: "JWT Token + Spring Security æ¦æªï¼æªææè¿å 401", status: "已宿½" }, |
| | | { item: "èå/æé®æé", impl: "ç¨æ·âè§è²âèåä¸çº§ï¼æé®çº§ perms æ ¡éª", status: "已宿½" }, |
| | | { item: "æ°æ®é离", impl: "å¤ç§æ·æä»¶èªå¨æ¼æ¥ tenant_id æ¡ä»¶", status: "已宿½" }, |
| | | { item: "æææä½å®¡è®¡", impl: "æ ·ååºå
¥åºãå®éªèµæºåé
ãåè¦å¤çå lims_audit_log", status: "已宿½" }, |
| | | { item: "审计ççå
容", impl: "æä½äººãIPãåæ®µåæ´ååå¼ãæä½ç»æ", status: "已宿½" }, |
| | | { item: "ç»å½æ¥å¿", impl: "è®°å½æ¯æ¬¡ç»å½ç IPãå°ç¹ãæµè§å¨ãæå失败", status: "已宿½" }, |
| | | { item: "åæ®µçº§å å¯", impl: "æªåï¼å¦å®¢æ·æçä¿è¦æ±éå¦è¡å¼å", status: "å¾
å®" }, |
| | | { item: "åè¡¨è±ææ¾ç¤º", impl: "åæ° security.maskSensitive å·²é¢çï¼å端å±ç¤ºæªå®ç°", status: "å¾
å®" }, |
| | | ]); |
| | | |
| | | const goSystem = (path) => { router.push("/system/" + path); }; |
| | | const goMonitor = (path) => { router.push("/monitor/" + path); }; |
| | | |
| | | // ç»è®¡ï¼ç¨å·²æçæ¥å£é¡ºæåæ°ï¼å¤±è´¥ä¸å½±åå±ç¤ºï¼ |
| | | const loadStat = () => { |
| | | Promise.all([ |
| | | import("@/api/system/user").then((m) => m.listUser({ pageNum: 1, pageSize: 1 })).catch(() => null), |
| | | import("@/api/system/role").then((m) => m.listRole({ pageNum: 1, pageSize: 1 })).catch(() => null), |
| | | import("@/api/system/menu").then((m) => m.listMenu({})).catch(() => null), |
| | | ]).then(([u, r, m]) => { |
| | | stat.value = { |
| | | userCount: u && u.total !== undefined ? u.total : (u && u.data && u.data.total), |
| | | roleCount: r && r.total !== undefined ? r.total : (r && r.data && r.data.total), |
| | | menuCount: Array.isArray(m) ? m.length : (m && m.data ? m.data.length : undefined), |
| | | }; |
| | | }).catch(() => {}); |
| | | }; |
| | | |
| | | /* ==================== å®å
¨çç¥ ==================== */ |
| | | const secSearch = ref({}); |
| | | const secData = ref([]); |
| | | const secLoading = ref(false); |
| | | const editVisible = ref(false); |
| | | const editForm = ref({}); |
| | | |
| | | const loadSecurity = () => { |
| | | secLoading.value = true; |
| | | listSetting({ ...secSearch.value, category: "security", pageNum: 1, pageSize: 50 }).then((res) => { |
| | | secLoading.value = false; |
| | | secData.value = res.data.records || []; |
| | | }).catch(() => (secLoading.value = false)); |
| | | }; |
| | | |
| | | const openEdit = (row) => { |
| | | editForm.value = { ...row }; |
| | | editVisible.value = true; |
| | | }; |
| | | const submitEdit = () => { |
| | | updateSetting(editForm.value).then(() => { |
| | | ElMessage.success("å·²ä¿åï¼å·æ°é¡µé¢åçæ"); |
| | | editVisible.value = false; |
| | | loadSecurity(); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | loadAudit(); |
| | | loadStat(); |
| | | loadSecurity(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search_form { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | } |
| | | .search_title { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | </style> |