✨ SFTP 操作日志.
This commit is contained in:
@@ -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}}
|
||||
|
||||
|
||||
###
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>"),
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
62
orion-ops-ui/src/api/asset/host-sftp-log.ts
Normal file
62
orion-ops-ui/src/api/asset/host-sftp-log.ts
Normal 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' });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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'),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
39
orion-ops-ui/src/views/host-audit/sftp-log/index.vue
Normal file
39
orion-ops-ui/src/views/host-audit/sftp-log/index.vue
Normal 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>
|
||||
14
orion-ops-ui/src/views/host-audit/sftp-log/types/const.ts
Normal file
14
orion-ops-ui/src/views/host-audit/sftp-log/types/const.ts
Normal 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];
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -33,7 +33,7 @@ const columns = [
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
}, {
|
||||
title: '操作地址',
|
||||
title: '留痕地址',
|
||||
dataIndex: 'address',
|
||||
slotName: 'address',
|
||||
width: 156,
|
||||
|
||||
Reference in New Issue
Block a user