SFTP 操作日志.

This commit is contained in:
lijiahang
2024-03-05 18:07:26 +08:00
parent a75ead9a58
commit 554c62abf7
22 changed files with 650 additions and 108 deletions

View File

@@ -1,5 +1,5 @@
### 分页查询 SFTP 操作日志
POST {{baseUrl}}/asset/sftp-log/query
POST {{baseUrl}}/asset/host-sftp-log/query
Content-Type: application/json
Authorization: {{token}}
@@ -9,4 +9,9 @@ Authorization: {{token}}
}
### 删除 SFTP 操作日志
DELETE {{baseUrl}}/asset/host-sftp-log/delete?idList=1,2,3
Authorization: {{token}}
###

View File

@@ -1,24 +1,25 @@
package com.orion.ops.module.asset.controller;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.ops.framework.common.validator.group.Page;
import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.asset.define.operator.HostTerminalOperatorType;
import com.orion.ops.module.asset.entity.request.host.HostSftpLogQueryRequest;
import com.orion.ops.module.asset.entity.vo.HostSftpLogVO;
import com.orion.ops.module.asset.service.HostSftpLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* SFTP 操作日志服务 api
@@ -32,7 +33,7 @@ import javax.annotation.Resource;
@Validated
@RestWrapper
@RestController
@RequestMapping("/asset/sftp-log")
@RequestMapping("/asset/host-sftp-log")
@SuppressWarnings({"ELValidationInJSP", "SpringElInspection"})
public class HostSftpLogController {
@@ -42,9 +43,18 @@ public class HostSftpLogController {
@IgnoreLog(IgnoreLogMode.RET)
@PostMapping("/query")
@Operation(summary = "分页查询 SFTP 操作日志")
@PreAuthorize("@ss.hasPermission('infra:operator-log:query')")
public DataGrid<HostSftpLogVO> querySftpLogPage(@Validated(Page.class) @RequestBody HostSftpLogQueryRequest request) {
return hostSftpLogService.querySftpLogPage(request);
@PreAuthorize("@ss.hasAnyPermission('infra:operator-log:query', 'asset:host-sftp-log:management:query')")
public DataGrid<HostSftpLogVO> getHostSftpLogPage(@Validated(Page.class) @RequestBody HostSftpLogQueryRequest request) {
return hostSftpLogService.getHostSftpLogPage(request);
}
@OperatorLog(HostTerminalOperatorType.DELETE_SFTP_LOG)
@DeleteMapping("/delete")
@Operation(summary = "删除 SFTP 操作日志")
@Parameter(name = "idList", description = "idList", required = true)
@PreAuthorize("@ss.hasAnyPermission('infra:operator-log:delete', 'asset:host-sftp-log:management:delete')")
public Integer deleteHostSftpLog(@RequestParam("idList") List<Long> idList) {
return hostSftpLogService.deleteHostSftpLog(idList);
}
}

View File

@@ -21,6 +21,8 @@ public class HostTerminalOperatorType extends InitializingOperatorTypes {
public static final String CONNECT = "host-terminal:connect";
public static final String DELETE_SFTP_LOG = "host-terminal:delete-sftp-log";
public static final String SFTP_MKDIR = "host-terminal:sftp-mkdir";
public static final String SFTP_TOUCH = "host-terminal:sftp-touch";
@@ -55,6 +57,7 @@ public class HostTerminalOperatorType extends InitializingOperatorTypes {
public OperatorType[] types() {
return new OperatorType[]{
new OperatorType(L, CONNECT, "连接主机 ${connectType} <sb>${hostName}</sb>"),
new OperatorType(H, DELETE_SFTP_LOG, "删除 SFTP 操作日志 <sb>${count}</sb> 条"),
new OperatorType(L, SFTP_MKDIR, "创建文件夹 ${hostName} <sb>${path}</sb>"),
new OperatorType(L, SFTP_TOUCH, "创建文件 ${hostName} <sb>${path}</sb>"),
new OperatorType(M, SFTP_MOVE, "移动文件 ${hostName} <sb>${path}</sb> 至 <sb>${target}</sb>"),

View File

@@ -32,14 +32,20 @@ public class HostSftpLogVO implements Serializable {
@Schema(description = "用户id")
private Long userId;
@Schema(description = "主机id")
private Long hostId;
@Schema(description = "用户名")
private String username;
@Schema(description = "traceId")
private String traceId;
@Schema(description = "主机id")
private Long hostId;
@Schema(description = "主机名称")
private String hostName;
@Schema(description = "主机地址")
private String hostAddress;
@Schema(description = "操作文件")
private String[] paths;
@Schema(description = "请求ip")
private String address;
@@ -50,40 +56,16 @@ public class HostSftpLogVO implements Serializable {
@Schema(description = "userAgent")
private String userAgent;
@Schema(description = "风险等级")
private String riskLevel;
@Schema(description = "模块")
private String module;
@Schema(description = "操作类型")
private String type;
@Schema(description = "日志")
private String logInfo;
@Schema(description = "参数")
private Map<String, Object> extra;
@Schema(description = "操作结果 0失败 1成功")
private Integer result;
@Schema(description = "错误信息")
private String errorMessage;
@Schema(description = "返回值")
private String returnValue;
@Schema(description = "操作时间")
private Integer duration;
@Schema(description = "开始时间")
private Date startTime;
@Schema(description = "结束时间")
private Date endTime;
@Schema(description = "创建时间")
private Date createTime;
}

View File

@@ -2,7 +2,6 @@ package com.orion.ops.module.asset.handler.host.terminal.handler;
import com.orion.lang.utils.collect.Maps;
import com.orion.ops.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.enums.BooleanBit;
import com.orion.ops.module.asset.define.operator.HostTerminalOperatorType;
import com.orion.ops.module.asset.handler.host.terminal.enums.OutputTypeEnum;
@@ -54,7 +53,7 @@ public class SftpRemoveHandler extends AbstractTerminalHandler<SftpBaseRequest>
.build());
// 保存操作日志
Map<String, Object> extra = Maps.newMap();
extra.put(OperatorLogs.PATH, String.join(Const.COMMA, paths));
extra.put(OperatorLogs.PATH, payload.getPath());
this.saveOperatorLog(payload, channel,
extra, HostTerminalOperatorType.SFTP_REMOVE,
startTime, ex);

View File

@@ -4,6 +4,8 @@ import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.module.asset.entity.request.host.HostSftpLogQueryRequest;
import com.orion.ops.module.asset.entity.vo.HostSftpLogVO;
import java.util.List;
/**
* SFTP 操作日志 服务类
*
@@ -19,6 +21,14 @@ public interface HostSftpLogService {
* @param request request
* @return rows
*/
DataGrid<HostSftpLogVO> querySftpLogPage(HostSftpLogQueryRequest request);
DataGrid<HostSftpLogVO> getHostSftpLogPage(HostSftpLogQueryRequest request);
/**
* 删除 SFTP 操作日志
*
* @param idList idList
* @return effect
*/
Integer deleteHostSftpLog(List<Long> idList);
}

View File

@@ -1,8 +1,12 @@
package com.orion.ops.module.asset.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.orion.lang.define.wrapper.DataGrid;
import com.orion.lang.utils.Arrays1;
import com.orion.lang.utils.Strings;
import com.orion.ops.framework.biz.operator.log.core.utils.OperatorLogs;
import com.orion.ops.framework.common.constant.ExtraFieldConst;
import com.orion.ops.module.asset.convert.HostSftpLogConvert;
import com.orion.ops.module.asset.define.operator.HostTerminalOperatorType;
import com.orion.ops.module.asset.entity.request.host.HostSftpLogQueryRequest;
@@ -33,7 +37,49 @@ public class HostSftpLogServiceImpl implements HostSftpLogService {
private OperatorLogApi operatorLogApi;
@Override
public DataGrid<HostSftpLogVO> querySftpLogPage(HostSftpLogQueryRequest request) {
public DataGrid<HostSftpLogVO> getHostSftpLogPage(HostSftpLogQueryRequest request) {
// 查询
OperatorLogQueryDTO query = this.buildQueryInfo(request);
DataGrid<OperatorLogDTO> dataGrid = operatorLogApi.getOperatorLogPage(query);
// 转换
List<HostSftpLogVO> rows = dataGrid.stream()
.map(s -> {
JSONObject extra = JSON.parseObject(s.getExtra());
HostSftpLogVO vo = HostSftpLogConvert.MAPPER.to(s);
vo.setHostId(extra.getLong(ExtraFieldConst.HOST_ID));
vo.setHostName(extra.getString(ExtraFieldConst.HOST_NAME));
vo.setHostAddress(extra.getString(ExtraFieldConst.ADDRESS));
vo.setPaths(extra.getString(ExtraFieldConst.PATH).split("\\|"));
vo.setExtra(extra);
return vo;
}).collect(Collectors.toList());
// 返回
DataGrid<HostSftpLogVO> result = new DataGrid<>();
result.setRows(rows);
result.setPage(dataGrid.getPage());
result.setLimit(dataGrid.getLimit());
result.setSize(dataGrid.getSize());
result.setTotal(dataGrid.getTotal());
return result;
}
@Override
public Integer deleteHostSftpLog(List<Long> idList) {
log.info("HostSftpLogService.deleteSftpLog start {}", JSON.toJSONString(idList));
Integer effect = operatorLogApi.deleteOperatorLog(idList);
log.info("HostSftpLogService.deleteSftpLog finish {}", effect);
// 设置日志参数
OperatorLogs.add(OperatorLogs.COUNT, effect);
return effect;
}
/**
* 构建查询对象
*
* @param request request
* @return query
*/
private OperatorLogQueryDTO buildQueryInfo(HostSftpLogQueryRequest request) {
Long hostId = request.getHostId();
String type = request.getType();
// 构建参数
@@ -55,19 +101,7 @@ public class HostSftpLogServiceImpl implements HostSftpLogService {
if (hostId != null) {
query.setExtra("\"hostId\": " + hostId + ",");
}
// 查询
DataGrid<OperatorLogDTO> dataGrid = operatorLogApi.getOperatorLogList(query);
// 返回
DataGrid<HostSftpLogVO> result = new DataGrid<>();
List<HostSftpLogVO> rows = dataGrid.stream()
.map(HostSftpLogConvert.MAPPER::to)
.collect(Collectors.toList());
result.setRows(rows);
result.setPage(dataGrid.getPage());
result.setLimit(dataGrid.getLimit());
result.setSize(dataGrid.getSize());
result.setTotal(dataGrid.getTotal());
return result;
return query;
}
}

View File

@@ -4,6 +4,8 @@ import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogDTO;
import com.orion.ops.module.infra.entity.dto.operator.OperatorLogQueryDTO;
import java.util.List;
/**
* 操作日志服务
*
@@ -14,11 +16,19 @@ import com.orion.ops.module.infra.entity.dto.operator.OperatorLogQueryDTO;
public interface OperatorLogApi {
/**
* 操作日志服务
* 分页查询操作日志
*
* @param request request
* @return rows
*/
DataGrid<OperatorLogDTO> getOperatorLogList(OperatorLogQueryDTO request);
DataGrid<OperatorLogDTO> getOperatorLogPage(OperatorLogQueryDTO request);
/**
* 删除操作日志
*
* @param idList idList
* @return effect
*/
Integer deleteOperatorLog(List<Long> idList);
}

View File

@@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* 操作日志服务实现
@@ -29,7 +30,7 @@ public class OperatorLogApiImpl implements OperatorLogApi {
private OperatorLogDAO operatorLogDAO;
@Override
public DataGrid<OperatorLogDTO> getOperatorLogList(OperatorLogQueryDTO request) {
public DataGrid<OperatorLogDTO> getOperatorLogPage(OperatorLogQueryDTO request) {
Valid.valid(request);
return operatorLogDAO.of()
.page(request)
@@ -37,6 +38,11 @@ public class OperatorLogApiImpl implements OperatorLogApi {
.dataGrid(OperatorLogProviderConvert.MAPPER::to);
}
@Override
public Integer deleteOperatorLog(List<Long> idList) {
return operatorLogDAO.deleteBatchIds(idList);
}
/**
* 构建查询 wrapper
*

View File

@@ -0,0 +1,62 @@
import type { DataGrid, Pagination } from '@/types/global';
import type { TableData } from '@arco-design/web-vue/es/table/interface';
import axios from 'axios';
import qs from 'query-string';
/**
* SFTP 操作日志 查询请求
*/
export interface HostSftpLogQueryRequest extends Pagination {
userId?: number;
hostId?: number;
type?: string;
result?: number;
startTimeRange?: string[];
}
/**
* SFTP 操作日志 查询响应
*/
export interface HostSftpLogQueryResponse extends TableData {
id: number;
userId: number;
username: number;
hostId: number;
hostName: string;
hostAddress: string;
address: string;
location: string;
userAgent: string;
paths: string[];
type: string;
result: string;
startTime: number;
extra: HostSftpLogExtra;
}
/**
* SFTP 操作日志 拓展对象
*/
export interface HostSftpLogExtra {
mod: number;
target: string;
}
/**
* 分页查询 SFTP 操作日志
*/
export function getHostSftpLogPage(request: HostSftpLogQueryRequest) {
return axios.post<DataGrid<HostSftpLogQueryResponse>>('/asset/host-sftp-log/query', request);
}
/**
* 删除 SFTP 操作日志
*/
export function deleteHostSftpLog(idList: Array<number>) {
return axios.delete('/asset/host-sftp-log/delete', {
params: { idList },
paramsSerializer: params => {
return qs.stringify(params, { arrayFormat: 'comma' });
}
});
}

View File

@@ -230,6 +230,20 @@ body {
margin-bottom: 16px;
}
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.text-copy {
&:hover {
cursor: pointer;
text-decoration: underline;
color: rgb(var(--arcoblue-6))
}
}
.copy-left, .copy-right {
color: rgb(var(--arcoblue-6));
cursor: pointer;

View File

@@ -12,6 +12,11 @@ const HOST_AUDIT: AppRouteRecordRaw =
path: '/host-audit/connect-log',
component: () => import('@/views/host-audit/connect-log/index.vue'),
},
{
name: 'hostAuditSftpLog',
path: '/host-audit/sftp-log',
component: () => import('@/views/host-audit/sftp-log/index.vue'),
},
],
};

View File

@@ -23,10 +23,11 @@
<a-descriptions-item label="连接主机">
<span>({{ record.hostId }}) {{ record.hostName }}</span>
<br>
<span class="copy-left" title="复制" @click="copy(record.hostAddress)">
<icon-copy />
<span class="host-address text-copy"
:title="record.hostAddress"
@click="copy(record.hostAddress)">
{{ record.hostAddress }}
</span>
<span class="host-address">{{ record.hostAddress }}</span>
</a-descriptions-item>
<!-- 连接类型 -->
<a-descriptions-item label="连接类型">
@@ -36,14 +37,15 @@
<a-descriptions-item label="连接状态">
{{ getDictValue(connectStatusKey, record.status) }}
</a-descriptions-item>
<!-- 连接地址 -->
<a-descriptions-item label="连接地址">
<!-- 留痕地址 -->
<a-descriptions-item label="留痕地址">
<span>{{ record.extra?.location }}</span>
<br>
<span class="copy-left" title="复制" @click="copy(record.extra?.address)">
<icon-copy />
<span class="connect-address text-copy"
:title="record.extra?.address"
@click="copy(record.extra?.address)">
{{ record.extra?.address }}
</span>
<span class="connect-address">{{ record.extra?.address }}</span>
</a-descriptions-item>
<!-- userAgent -->
<a-descriptions-item label="userAgent">
@@ -63,15 +65,21 @@
</a-descriptions-item>
<!-- traceId -->
<a-descriptions-item label="traceId">
{{ record.extra?.traceId }}
<span class="text-copy" @click="copy(record.extra?.traceId)">
{{ record.extra?.traceId }}
</span>
</a-descriptions-item>
<!-- channelId -->
<a-descriptions-item label="channelId">
{{ record.extra?.channelId }}
<span class="text-copy" @click="copy(record.extra?.channelId)">
{{ record.extra?.channelId }}
</span>
</a-descriptions-item>
<!-- sessionId -->
<a-descriptions-item label="sessionId">
{{ record.extra?.sessionId }}
<span class="text-copy" @click="copy(record.extra?.sessionId)">
{{ record.extra?.sessionId }}
</span>
</a-descriptions-item>
</a-descriptions>
</a-drawer>

View File

@@ -3,7 +3,7 @@
<a-card class="general-card table-search-card">
<query-header :model="formModel"
label-align="left"
:itemOptions="{ 6: { span: 2 } }"
:itemOptions="{ 5: { span: 2 } }"
@submit="fetchTableData"
@reset="fetchTableData"
@keyup.enter="() => fetchTableData()">
@@ -99,19 +99,6 @@
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
@page-size-change="(size) => fetchTableData(1, size)"
:bordered="false">
<!-- 连接地址 -->
<template #address="{ record }">
<span class="connect-location" :title="record.extra?.location">
{{ record.extra?.location }}
</span>
<br>
<span class="copy-left" title="复制" @click="copy(record.extra?.address)">
<icon-copy />
</span>
<span class="connect-address" :title="record.extra?.address">
{{ record.extra?.address }}
</span>
</template>
<!-- 连接用户 -->
<template #username="{ record }">
{{ record.username }}
@@ -122,10 +109,9 @@
{{ record.hostName }}
</span>
<br>
<span class="copy-left" title="复制" @click="copy(record.hostAddress)">
<icon-copy />
</span>
<span class="host-address" :title="record.hostAddress">
<span class="host-address text-copy"
:title="record.hostAddress"
@click="copy(record.hostAddress)">
{{ record.hostAddress }}
</span>
</template>
@@ -136,6 +122,18 @@
}" />
{{ getDictValue(connectStatusKey, record.status) }}
</template>
<!-- 留痕地址 -->
<template #address="{ record }">
<span class="connect-location" :title="record.extra?.location">
{{ record.extra?.location }}
</span>
<br>
<span class="connect-address text-copy"
:title="record.extra?.address"
@click="copy(record.extra?.address)">
{{ record.extra?.address }}
</span>
</template>
<!-- 操作 -->
<template #handle="{ record }">
<div class="table-handle-wrapper">
@@ -203,8 +201,6 @@
import ConnectLogClearModal from './connect-log-clear-modal.vue';
import ConnectLogDetailDrawer from './connect-log-detail-drawer.vue';
const emits = defineEmits(['openAdd', 'openUpdate']);
const tableRenderData = ref<HostConnectLogQueryResponse[]>([]);
const selectedKeys = ref<number[]>([]);
const clearModal = ref();

View File

@@ -3,17 +3,10 @@ import { dateFormat } from '@/utils';
const columns = [
{
title: '连接地址',
dataIndex: 'address',
slotName: 'address',
width: 156,
align: 'left',
ellipsis: true,
}, {
title: '连接用户',
dataIndex: 'username',
slotName: 'username',
width: 180,
width: 140,
align: 'left',
ellipsis: true,
}, {
@@ -34,6 +27,13 @@ const columns = [
slotName: 'status',
align: 'left',
width: 106,
}, {
title: '留痕地址',
dataIndex: 'address',
slotName: 'address',
width: 156,
align: 'left',
ellipsis: true,
}, {
title: '连接时间',
dataIndex: 'connectTime',

View File

@@ -0,0 +1,286 @@
<template>
<!-- 搜索 -->
<a-card class="general-card table-search-card">
<query-header :model="formModel"
label-align="left"
:itemOptions="{ 4: { span: 2 } }"
@submit="fetchTableData"
@reset="fetchTableData"
@keyup.enter="() => fetchTableData()">
<!-- 操作用户 -->
<a-form-item field="userId" label="操作用户" label-col-flex="50px">
<user-selector v-model="formModel.userId"
placeholder="请选择用户"
allow-clear />
</a-form-item>
<!-- 操作主机 -->
<a-form-item field="hostId" label="操作主机" label-col-flex="50px">
<host-selector v-model="formModel.hostId"
placeholder="请选择主机"
allow-clear />
</a-form-item>
<!-- 操作类型 -->
<a-form-item field="type" label="操作类型" label-col-flex="50px">
<a-select v-model="formModel.type"
placeholder="请选择类型"
:options="toOptions(sftpOperatorTypeKey)"
allow-clear />
</a-form-item>
<!-- 执行结果 -->
<a-form-item field="result" label="执行结果" label-col-flex="50px">
<a-select v-model="formModel.result"
placeholder="请选择执行结果"
:options="toOptions(sftpOperatorResultKey)"
allow-clear />
</a-form-item>
<!-- 开始时间 -->
<a-form-item field="startTimeRange" label="开始时间" label-col-flex="50px">
<a-range-picker v-model="formModel.startTimeRange"
style="width: 100%"
:time-picker-props="{ defaultValue: ['00:00:00', '23:59:59'] }"
show-time
format="YYYY-MM-DD HH:mm:ss" />
</a-form-item>
</query-header>
</a-card>
<!-- 表格 -->
<a-card class="general-card table-card">
<template #title>
<!-- 左侧操作 -->
<div class="table-left-bar-handle">
<!-- 标题 -->
<div class="table-title">
SFTP 操作日志
</div>
</div>
<!-- 右侧操作 -->
<div class="table-right-bar-handle">
<a-space>
<!-- 删除 -->
<a-popconfirm :content="`确认删除选中的 ${selectedKeys.length} 条记录吗?`"
position="br"
type="warning"
@ok="deleteSelectRows">
<a-button v-permission="['infra:operator-log:delete', 'asset:host-sftp-log:management:delete']"
type="secondary"
status="danger"
:disabled="selectedKeys.length === 0">
删除
<template #icon>
<icon-delete />
</template>
</a-button>
</a-popconfirm>
</a-space>
</div>
</template>
<!-- table -->
<a-table row-key="id"
ref="tableRef"
:loading="loading"
v-model:selected-keys="selectedKeys"
:row-selection="rowSelection"
:columns="columns"
:data="tableRenderData"
:pagination="pagination"
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
@page-size-change="(size) => fetchTableData(1, size)"
:bordered="false">
<!-- 操作用户 -->
<template #username="{ record }">
{{ record.username }}
</template>
<!-- 操作主机 -->
<template #hostName="{ record }">
<span class="host-name" :title="record.hostName">
{{ record.hostName }}
</span>
<br>
<span class="host-address text-copy"
:title="record.hostAddress"
@click="copy(record.hostAddress)">
{{ record.hostAddress }}
</span>
</template>
<!-- 操作类型 -->
<template #type="{ record }">
{{ getDictValue(sftpOperatorTypeKey, record.type) }}
</template>
<!-- 操作文件 -->
<template #paths="{ record }">
<div class="paths-wrapper">
<span v-for="path in record.paths"
class="path-wrapper text-ellipsis text-copy"
:title="path"
@click="copy(path)">
{{ path }}
</span>
<!-- 移动目标路径 -->
<span class="sub-text" v-if="SftpOperatorType.SFTP_MOVE === record.type">
移动到 {{ record.extra?.target }}
</span>
<!-- 提权信息 -->
<span class="sub-text" v-if="SftpOperatorType.SFTP_CHMOD === record.type">
提权 {{ record.extra?.mod }} {{ permission10toString(record.extra?.mod as number) }}
</span>
</div>
</template>
<!-- 执行结果 -->
<template #result="{ record }">
<a-tag :color="getDictValue(sftpOperatorResultKey, record.result, 'color')">
{{ getDictValue(sftpOperatorResultKey, record.result) }}
</a-tag>
</template>
<!-- 留痕地址 -->
<template #address="{ record }">
<span class="operator-location" :title="record.location">
{{ record.location }}
</span>
<br>
<span class="operator-address text-copy"
:title="record.address"
@click="copy(record.address)">
{{ record.address }}
</span>
</template>
<!-- 操作 -->
<template #handle="{ record }">
<div class="table-handle-wrapper">
<!-- 删除 -->
<a-popconfirm content="确认删除这条记录吗?"
position="left"
type="warning"
@ok="deleteRow(record)">
<a-button v-permission="['infra:operator-log:delete', 'asset:host-sftp-log:management:delete']"
type="text"
size="mini"
status="danger">
删除
</a-button>
</a-popconfirm>
</div>
</template>
</a-table>
</a-card>
</template>
<script lang="ts">
export default {
name: 'hostAuditSftpLogTable'
};
</script>
<script lang="ts" setup>
import type { HostSftpLogQueryRequest, HostSftpLogQueryResponse } from '@/api/asset/host-sftp-log';
import { reactive, ref, onMounted } from 'vue';
import { getHostSftpLogPage, deleteHostSftpLog } from '@/api/asset/host-sftp-log';
import { sftpOperatorTypeKey, sftpOperatorResultKey, SftpOperatorType } from '../types/const';
import { usePagination, useRowSelection } from '@/types/table';
import { useDictStore } from '@/store';
import { Message } from '@arco-design/web-vue';
import columns from '../types/table.columns';
import useLoading from '@/hooks/loading';
import useCopy from '@/hooks/copy';
import UserSelector from '@/components/user/user/user-selector.vue';
import HostSelector from '@/components/asset/host/host-selector.vue';
import { permission10toString } from '@/utils/file';
const tableRenderData = ref<HostSftpLogQueryResponse[]>([]);
const selectedKeys = ref<number[]>([]);
const pagination = usePagination();
const rowSelection = useRowSelection();
const { loading, setLoading } = useLoading();
const { toOptions, getDictValue } = useDictStore();
const { copy } = useCopy();
const formModel = reactive<HostSftpLogQueryRequest>({
userId: undefined,
hostId: undefined,
type: undefined,
result: undefined,
startTimeRange: undefined,
});
// 加载数据
const doFetchTableData = async (request: HostSftpLogQueryRequest) => {
try {
setLoading(true);
const { data } = await getHostSftpLogPage(request);
tableRenderData.value = data.rows;
pagination.total = data.total;
pagination.current = request.page;
pagination.pageSize = request.limit;
selectedKeys.value = [];
} catch (e) {
} finally {
setLoading(false);
}
};
// 切换页码
const fetchTableData = (page = 1, limit = pagination.pageSize, form = formModel) => {
doFetchTableData({ page, limit, ...form });
};
// 删除选中行
const deleteSelectRows = async () => {
try {
setLoading(true);
// 调用删除接口
await deleteHostSftpLog(selectedKeys.value);
Message.success(`成功删除 ${selectedKeys.value.length} 条数据`);
selectedKeys.value = [];
// 重新加载数据
fetchTableData();
} catch (e) {
} finally {
setLoading(false);
}
};
// 删除当前行
const deleteRow = async ({ id }: {
id: number
}) => {
try {
setLoading(true);
// 调用删除接口
await deleteHostSftpLog([id]);
Message.success('删除成功');
selectedKeys.value = [];
// 重新加载数据
fetchTableData();
} catch (e) {
} finally {
setLoading(false);
}
};
onMounted(() => {
fetchTableData();
});
</script>
<style lang="less" scoped>
.host-name, .operator-location {
color: var(--color-text-2);
}
.host-address, .operator-address, .sub-text {
margin-top: 4px;
display: inline-block;
color: var(--color-text-3);
}
.paths-wrapper {
display: flex;
flex-direction: column;
.path-wrapper {
display: block;
padding: 2px;
}
}
</style>

View File

@@ -0,0 +1,39 @@
<template>
<div class="layout-container" v-if="render">
<!-- 列表-表格 -->
<sftp-log-table />
</div>
</template>
<script lang="ts">
export default {
name: 'hostAuditSftpLog'
};
</script>
<script lang="ts" setup>
import { ref, onBeforeMount, onUnmounted } from 'vue';
import { useCacheStore, useDictStore } from '@/store';
import { dictKeys } from './types/const';
import SftpLogTable from './components/sftp-log-table.vue';
const render = ref(false);
// 加载字典配置
onBeforeMount(async () => {
const dictStore = useDictStore();
await dictStore.loadKeys(dictKeys);
render.value = true;
});
// 重置缓存
onUnmounted(() => {
const cacheStore = useCacheStore();
cacheStore.reset('users', 'hosts');
});
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,14 @@
// sftp 操作类型
export const SftpOperatorType = {
SFTP_MOVE: 'host-terminal:sftp-move',
SFTP_CHMOD: 'host-terminal:sftp-chmod',
};
// sftp 操作类型 字典项
export const sftpOperatorTypeKey = 'sftpOperatorType';
// sftp 操作结果 字典项
export const sftpOperatorResultKey = 'operatorLogResult';
// 加载的字典值
export const dictKeys = [sftpOperatorTypeKey, sftpOperatorResultKey];

View File

@@ -0,0 +1,61 @@
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
import { dateFormat } from '@/utils';
const columns = [
{
title: '操作用户',
dataIndex: 'username',
slotName: 'username',
width: 140,
align: 'left',
ellipsis: true,
}, {
title: '操作主机',
dataIndex: 'hostName',
slotName: 'hostName',
width: 180,
align: 'left',
ellipsis: true,
}, {
title: '操作类型',
dataIndex: 'type',
slotName: 'type',
width: 116,
align: 'left',
}, {
title: '操作文件',
dataIndex: 'paths',
slotName: 'paths',
align: 'left',
}, {
title: '执行结果',
dataIndex: 'result',
slotName: 'result',
align: 'left',
width: 88,
}, {
title: '留痕地址',
dataIndex: 'address',
slotName: 'address',
width: 156,
align: 'left',
ellipsis: true,
}, {
title: '操作时间',
dataIndex: 'startTime',
slotName: 'startTime',
align: 'center',
width: 180,
render: ({ record }) => {
return (record.startTime && dateFormat(new Date(record.startTime)));
},
}, {
title: '操作',
slotName: 'handle',
width: 80,
align: 'center',
fixed: 'right',
},
] as TableColumnData[];
export default columns;

View File

@@ -32,16 +32,15 @@
<icon-copy class="copy-left" @click="copy(record.originLogInfo, '已复制')" />
<span v-html="replaceHtmlTag(record.logInfo)" />
</template>
<!-- 操作地址 -->
<!-- 留痕地址 -->
<template #address="{ record }">
<span class="operator-location" :title="record.location">
{{ record.location }}
</span>
<br>
<span class="copy-left" title="复制" @click="copy(record.address)">
<icon-copy />
</span>
<span class="operator-address" :title="record.address">
<span class="operator-address text-copy"
:title="record.address"
@click="copy(record.address)">
{{ record.address }}
</span>
</template>

View File

@@ -79,16 +79,15 @@
<icon-copy class="copy-left" @click="copy(record.originLogInfo, '已复制')" />
<span v-html="replaceHtmlTag(record.logInfo)" />
</template>
<!-- 操作地址 -->
<!-- 留痕地址 -->
<template #address="{ record }">
<span class="operator-location" :title="record.location">
{{ record.location }}
</span>
<br>
<span class="copy-left" title="复制" @click="copy(record.address)">
<icon-copy />
</span>
<span class="operator-address" :title="record.address">
<span class="operator-address text-copy"
:title="record.address"
@click="copy(record.address)">
{{ record.address }}
</span>
</template>

View File

@@ -33,7 +33,7 @@ const columns = [
ellipsis: true,
tooltip: true,
}, {
title: '操作地址',
title: '留痕地址',
dataIndex: 'address',
slotName: 'address',
width: 156,