💄 优化表格样式.
This commit is contained in:
@@ -1,12 +1,25 @@
|
|||||||
> 版本号严格遵循 Semver 规范。
|
> 版本号严格遵循 Semver 规范。
|
||||||
|
|
||||||
[//]: # (🐞修复)
|
## v1.0.1
|
||||||
|
|
||||||
|
`2024-03-` `release`
|
||||||
|
|
||||||
|
🐞 修复 用户操作日志条件重置后类型框数据不正常的问题
|
||||||
|
🩰 修改 主机连接日志 UI
|
||||||
|
🌈 新增 SFTP 使用日志列表
|
||||||
|
🌈 新增 主机连接日志强制下线会话
|
||||||
|
🌈 新增 主机连接日志删除/清理
|
||||||
|
🌈 新增 用户操作日志日志删除/清理
|
||||||
|
🌈 新增 用户操作日志日志删除/清理
|
||||||
|
🔨 优化 用户锁定次数/时间可配置
|
||||||
|
|
||||||
|
[如何升级](/about/update.md?id=_v101)
|
||||||
|
|
||||||
## v1.0.0
|
## v1.0.0
|
||||||
|
|
||||||
`2024-03-01` `release`
|
`2024-03-01` `release`
|
||||||
|
|
||||||
🌈 用户自定义终端标签颜色
|
🌈 新增 用户自定义终端标签颜色
|
||||||
🔨 拓展数据模块添加缓存
|
🔨 拓展数据模块添加缓存
|
||||||
|
|
||||||
[如何升级](/about/update.md?id=_v100)
|
[如何升级](/about/update.md?id=_v100)
|
||||||
|
|||||||
@@ -80,7 +80,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- table -->
|
<!-- table -->
|
||||||
<a-table row-key="id"
|
<a-table row-key="id"
|
||||||
class="table-wrapper-8"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public enum OutputTypeEnum {
|
|||||||
/**
|
/**
|
||||||
* 关闭连接
|
* 关闭连接
|
||||||
*/
|
*/
|
||||||
CLOSE("cl", "${type}|${sessionId}|${msg}"),
|
CLOSE("cl", "${type}|${sessionId}|${forceClose}|${msg}"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pong
|
* pong
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ import lombok.experimental.SuperBuilder;
|
|||||||
@Schema(name = "TerminalCloseResponse", description = "主机连接关闭响应 实体对象")
|
@Schema(name = "TerminalCloseResponse", description = "主机连接关闭响应 实体对象")
|
||||||
public class TerminalCloseResponse extends TerminalBasePayload {
|
public class TerminalCloseResponse extends TerminalBasePayload {
|
||||||
|
|
||||||
|
@Schema(description = "是否为强制关闭")
|
||||||
|
private Integer forceClose;
|
||||||
|
|
||||||
@Schema(description = "关闭信息")
|
@Schema(description = "关闭信息")
|
||||||
private String msg;
|
private String msg;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.orion.lang.utils.io.Streams;
|
|||||||
import com.orion.net.host.SessionStore;
|
import com.orion.net.host.SessionStore;
|
||||||
import com.orion.net.host.ssh.shell.ShellExecutor;
|
import com.orion.net.host.ssh.shell.ShellExecutor;
|
||||||
import com.orion.ops.framework.common.constant.Const;
|
import com.orion.ops.framework.common.constant.Const;
|
||||||
|
import com.orion.ops.framework.common.enums.BooleanBit;
|
||||||
import com.orion.ops.framework.websocket.core.utils.WebSockets;
|
import com.orion.ops.framework.websocket.core.utils.WebSockets;
|
||||||
import com.orion.ops.module.asset.define.AssetThreadPools;
|
import com.orion.ops.module.asset.define.AssetThreadPools;
|
||||||
import com.orion.ops.module.asset.handler.host.terminal.constant.TerminalMessage;
|
import com.orion.ops.module.asset.handler.host.terminal.constant.TerminalMessage;
|
||||||
@@ -53,7 +54,7 @@ public class SshSession extends TerminalSession implements ISshSession {
|
|||||||
executor.size(cols, rows);
|
executor.size(cols, rows);
|
||||||
executor.terminalType(terminalType);
|
executor.terminalType(terminalType);
|
||||||
executor.streamHandler(this::streamHandler);
|
executor.streamHandler(this::streamHandler);
|
||||||
executor.callback(this::eofCallback);
|
executor.callback(this::close);
|
||||||
executor.connect();
|
executor.connect();
|
||||||
// 开始监听输出
|
// 开始监听输出
|
||||||
AssetThreadPools.TERMINAL_STDOUT.execute(executor);
|
AssetThreadPools.TERMINAL_STDOUT.execute(executor);
|
||||||
@@ -122,20 +123,4 @@ public class SshSession extends TerminalSession implements ISshSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* eof 回调
|
|
||||||
*/
|
|
||||||
private void eofCallback() {
|
|
||||||
log.info("terminal eof回调 {}, forClose: {}, forceOffline: {}", sessionId, this.close, this.forceOffline);
|
|
||||||
// 发送关闭信息
|
|
||||||
TerminalCloseResponse resp = TerminalCloseResponse.builder()
|
|
||||||
.type(OutputTypeEnum.CLOSE.getType())
|
|
||||||
.sessionId(this.sessionId)
|
|
||||||
.msg(this.forceOffline ? TerminalMessage.FORCED_OFFLINE : TerminalMessage.CLOSED_CONNECTION)
|
|
||||||
.build();
|
|
||||||
WebSockets.sendText(channel, OutputTypeEnum.CLOSE.format(resp));
|
|
||||||
// 需要调用关闭 - 可能是 logout 需要手动触发
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package com.orion.ops.module.asset.handler.host.terminal.session;
|
package com.orion.ops.module.asset.handler.host.terminal.session;
|
||||||
|
|
||||||
|
import com.orion.ops.framework.common.enums.BooleanBit;
|
||||||
|
import com.orion.ops.framework.websocket.core.utils.WebSockets;
|
||||||
import com.orion.ops.module.asset.enums.HostConnectStatusEnum;
|
import com.orion.ops.module.asset.enums.HostConnectStatusEnum;
|
||||||
|
import com.orion.ops.module.asset.handler.host.terminal.constant.TerminalMessage;
|
||||||
|
import com.orion.ops.module.asset.handler.host.terminal.enums.OutputTypeEnum;
|
||||||
import com.orion.ops.module.asset.handler.host.terminal.model.TerminalConfig;
|
import com.orion.ops.module.asset.handler.host.terminal.model.TerminalConfig;
|
||||||
|
import com.orion.ops.module.asset.handler.host.terminal.model.response.TerminalCloseResponse;
|
||||||
import com.orion.ops.module.asset.service.HostConnectLogService;
|
import com.orion.ops.module.asset.service.HostConnectLogService;
|
||||||
import com.orion.spring.SpringHolder;
|
import com.orion.spring.SpringHolder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -41,6 +46,21 @@ public abstract class TerminalSession implements ITerminalSession {
|
|||||||
*/
|
*/
|
||||||
protected abstract void releaseResource();
|
protected abstract void releaseResource();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送关闭消息
|
||||||
|
*/
|
||||||
|
protected void sendCloseMessage() {
|
||||||
|
log.info("TerminalSession close {}, forClose: {}, forceOffline: {}", sessionId, this.close, this.forceOffline);
|
||||||
|
// 发送关闭信息
|
||||||
|
TerminalCloseResponse resp = TerminalCloseResponse.builder()
|
||||||
|
.type(OutputTypeEnum.CLOSE.getType())
|
||||||
|
.sessionId(this.sessionId)
|
||||||
|
.forceClose(BooleanBit.of(this.forceOffline).getValue())
|
||||||
|
.msg(this.forceOffline ? TerminalMessage.FORCED_OFFLINE : TerminalMessage.CLOSED_CONNECTION)
|
||||||
|
.build();
|
||||||
|
WebSockets.sendText(channel, OutputTypeEnum.CLOSE.format(resp));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
log.info("terminal close {}", sessionId);
|
log.info("terminal close {}", sessionId);
|
||||||
@@ -56,6 +76,7 @@ public abstract class TerminalSession implements ITerminalSession {
|
|||||||
public void forceOffline() {
|
public void forceOffline() {
|
||||||
log.info("terminal forceOffline {}", sessionId);
|
log.info("terminal forceOffline {}", sessionId);
|
||||||
this.forceOffline = true;
|
this.forceOffline = true;
|
||||||
|
// 关闭
|
||||||
this.checkAndClose();
|
this.checkAndClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +96,12 @@ public abstract class TerminalSession implements ITerminalSession {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("terminal release error {}", sessionId, e);
|
log.error("terminal release error {}", sessionId, e);
|
||||||
}
|
}
|
||||||
|
// 发送关闭信息
|
||||||
|
try {
|
||||||
|
this.sendCloseMessage();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("terminal send close error {}", sessionId, e);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,12 +85,25 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
|
|||||||
HostConnectLogDO record = hostConnectLogDAO.of()
|
HostConnectLogDO record = hostConnectLogDAO.of()
|
||||||
.createWrapper()
|
.createWrapper()
|
||||||
.eq(HostConnectLogDO::getToken, token)
|
.eq(HostConnectLogDO::getToken, token)
|
||||||
|
.orderByDesc(HostConnectLogDO::getId)
|
||||||
.then()
|
.then()
|
||||||
.getOne();
|
.getOne();
|
||||||
if (record == null) {
|
if (record == null) {
|
||||||
log.info("HostConnectLogService-updateStatusByToken no record token: {}", token);
|
log.info("HostConnectLogService-updateStatusByToken no record token: {}", token);
|
||||||
return Const.N_0;
|
return Const.N_0;
|
||||||
}
|
}
|
||||||
|
return this.updateStatus(record, status, partial);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新状态
|
||||||
|
*
|
||||||
|
* @param record record
|
||||||
|
* @param status status
|
||||||
|
* @param partial partial
|
||||||
|
* @return effect
|
||||||
|
*/
|
||||||
|
private int updateStatus(HostConnectLogDO record, HostConnectStatusEnum status, Map<String, Object> partial) {
|
||||||
// 更新
|
// 更新
|
||||||
HostConnectLogDO update = new HostConnectLogDO();
|
HostConnectLogDO update = new HostConnectLogDO();
|
||||||
update.setId(record.getId());
|
update.setId(record.getId());
|
||||||
@@ -151,20 +164,20 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
|
|||||||
public Integer forceOffline(HostConnectLogQueryRequest request) {
|
public Integer forceOffline(HostConnectLogQueryRequest request) {
|
||||||
Long id = request.getId();
|
Long id = request.getId();
|
||||||
// 查询数据是否存在
|
// 查询数据是否存在
|
||||||
HostConnectLogDO connect = hostConnectLogDAO.selectById(id);
|
HostConnectLogDO record = hostConnectLogDAO.selectById(id);
|
||||||
Valid.notNull(connect, ErrorMessage.LOG_ABSENT);
|
Valid.notNull(record, ErrorMessage.LOG_ABSENT);
|
||||||
Valid.eq(connect.getStatus(), HostConnectStatusEnum.CONNECTING.name(), ErrorMessage.ILLEGAL_STATUS);
|
Valid.eq(record.getStatus(), HostConnectStatusEnum.CONNECTING.name(), ErrorMessage.ILLEGAL_STATUS);
|
||||||
// 设置日志参数
|
// 设置日志参数
|
||||||
OperatorLogs.add(OperatorLogs.HOST_NAME, connect.getHostName());
|
OperatorLogs.add(OperatorLogs.HOST_NAME, record.getHostName());
|
||||||
// 获取会话
|
// 获取会话
|
||||||
HostConnectLogExtraDTO extra = JSON.parseObject(connect.getExtraInfo(), HostConnectLogExtraDTO.class);
|
HostConnectLogExtraDTO extra = JSON.parseObject(record.getExtraInfo(), HostConnectLogExtraDTO.class);
|
||||||
ITerminalSession session = terminalManager.getSession(extra.getChannelId(), extra.getSessionId());
|
ITerminalSession session = terminalManager.getSession(extra.getChannelId(), extra.getSessionId());
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
// 关闭会话
|
// 关闭会话
|
||||||
session.forceOffline();
|
session.forceOffline();
|
||||||
}
|
}
|
||||||
// 更新状态
|
// 更新状态
|
||||||
return this.updateStatusByToken(connect.getToken(), HostConnectStatusEnum.FORCE_OFFLINE, null);
|
return this.updateStatus(record, HostConnectStatusEnum.FORCE_OFFLINE, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryReques
|
|||||||
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
|
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
|
||||||
import com.orion.ops.module.infra.entity.vo.OperatorLogVO;
|
import com.orion.ops.module.infra.entity.vo.OperatorLogVO;
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.Mapping;
|
|
||||||
import org.mapstruct.factory.Mappers;
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,7 +24,6 @@ public interface OperatorLogConvert {
|
|||||||
|
|
||||||
OperatorLogDO to(OperatorLogQueryRequest request);
|
OperatorLogDO to(OperatorLogQueryRequest request);
|
||||||
|
|
||||||
@Mapping(target = "extra", ignore = true)
|
|
||||||
OperatorLogVO to(OperatorLogDO domain);
|
OperatorLogVO to(OperatorLogDO domain);
|
||||||
|
|
||||||
LoginHistoryVO toLoginHistory(OperatorLogDO domain);
|
LoginHistoryVO toLoginHistory(OperatorLogDO domain);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import lombok.NoArgsConstructor;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作日志 视图响应对象
|
* 操作日志 视图响应对象
|
||||||
@@ -60,7 +59,7 @@ public class OperatorLogVO implements Serializable {
|
|||||||
private String logInfo;
|
private String logInfo;
|
||||||
|
|
||||||
@Schema(description = "参数")
|
@Schema(description = "参数")
|
||||||
private Map<String, Object> extra;
|
private String extra;
|
||||||
|
|
||||||
@Schema(description = "操作结果 0失败 1成功")
|
@Schema(description = "操作结果 0失败 1成功")
|
||||||
private Integer result;
|
private Integer result;
|
||||||
|
|||||||
@@ -50,11 +50,7 @@ public class OperatorLogServiceImpl implements OperatorLogService {
|
|||||||
// 查询
|
// 查询
|
||||||
return operatorLogDAO.of(wrapper)
|
return operatorLogDAO.of(wrapper)
|
||||||
.page(request)
|
.page(request)
|
||||||
.dataGrid(s -> {
|
.dataGrid(OperatorLogConvert.MAPPER::to);
|
||||||
OperatorLogVO vo = OperatorLogConvert.MAPPER.to(s);
|
|
||||||
vo.setExtra(JSON.parseObject(s.getExtra()));
|
|
||||||
return vo;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- table -->
|
<!-- table -->
|
||||||
<a-table row-key="id"
|
<a-table row-key="id"
|
||||||
class="table-wrapper-8"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|||||||
@@ -72,7 +72,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- table -->
|
<!-- table -->
|
||||||
<a-table row-key="id"
|
<a-table row-key="id"
|
||||||
class="table-wrapper-8"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|||||||
@@ -64,7 +64,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- table -->
|
<!-- table -->
|
||||||
<a-table row-key="id"
|
<a-table row-key="id"
|
||||||
class="table-wrapper-8"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|||||||
@@ -91,7 +91,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- table -->
|
<!-- table -->
|
||||||
<a-table row-key="id"
|
<a-table row-key="id"
|
||||||
class="table-wrapper-8"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|||||||
@@ -89,7 +89,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- table -->
|
<!-- table -->
|
||||||
<a-table row-key="id"
|
<a-table row-key="id"
|
||||||
class="table-wrapper-8"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
|
|||||||
@@ -44,8 +44,12 @@
|
|||||||
</a-breadcrumb>
|
</a-breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 已关闭-右侧操作 -->
|
||||||
|
<div v-if="isClose" class="sftp-table-header-right">
|
||||||
|
<span class="close-message">{{ closeMessage }}</span>
|
||||||
|
</div>
|
||||||
<!-- 路径编辑模式-右侧操作 -->
|
<!-- 路径编辑模式-右侧操作 -->
|
||||||
<a-space v-if="pathEditable" class="sftp-table-header-right">
|
<a-space v-else-if="pathEditable" class="sftp-table-header-right">
|
||||||
<!-- 进入 -->
|
<!-- 进入 -->
|
||||||
<a-tooltip position="top"
|
<a-tooltip position="top"
|
||||||
:mini="true"
|
:mini="true"
|
||||||
@@ -185,9 +189,11 @@
|
|||||||
import { openSftpCreateModalKey, openSftpUploadModalKey } from '../../types/terminal.const';
|
import { openSftpCreateModalKey, openSftpUploadModalKey } from '../../types/terminal.const';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
isClose: boolean;
|
||||||
|
closeMessage: string | undefined;
|
||||||
currentPath: string;
|
currentPath: string;
|
||||||
session: ISftpSession | undefined,
|
session: ISftpSession | undefined;
|
||||||
selectedFiles: Array<string>
|
selectedFiles: Array<string>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emits = defineEmits(['update:selectedFiles', 'loadFile', 'download']);
|
const emits = defineEmits(['update:selectedFiles', 'loadFile', 'download']);
|
||||||
@@ -322,6 +328,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.close-message {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-left: 16px;
|
||||||
|
color: rgb(var(--red-6));
|
||||||
|
}
|
||||||
|
|
||||||
.header-action-icon {
|
.header-action-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
<!-- 表头 -->
|
<!-- 表头 -->
|
||||||
<sftp-table-header class="sftp-table-header"
|
<sftp-table-header class="sftp-table-header"
|
||||||
v-model:selected-files="selectFiles"
|
v-model:selected-files="selectFiles"
|
||||||
|
:is-close="closed"
|
||||||
|
:close-message="closeMessage"
|
||||||
:current-path="currentPath"
|
:current-path="currentPath"
|
||||||
:session="session"
|
:session="session"
|
||||||
@load-file="loadFiles"
|
@load-file="loadFiles"
|
||||||
@@ -89,6 +91,8 @@
|
|||||||
const fileList = ref<Array<SftpFile>>([]);
|
const fileList = ref<Array<SftpFile>>([]);
|
||||||
const selectFiles = ref<Array<string>>([]);
|
const selectFiles = ref<Array<string>>([]);
|
||||||
const splitSize = ref(1);
|
const splitSize = ref(1);
|
||||||
|
const closed = ref(false);
|
||||||
|
const closeMessage = ref('');
|
||||||
const editorView = ref(false);
|
const editorView = ref(false);
|
||||||
const editorRef = ref();
|
const editorRef = ref();
|
||||||
const editorFileName = ref('');
|
const editorFileName = ref('');
|
||||||
@@ -184,6 +188,14 @@
|
|||||||
return success;
|
return success;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 关闭回调
|
||||||
|
const onClose = (forceClose: string, msg: string) => {
|
||||||
|
console.log(forceClose);
|
||||||
|
console.log(msg);
|
||||||
|
closed.value = true;
|
||||||
|
closeMessage.value = msg;
|
||||||
|
};
|
||||||
|
|
||||||
// 接收列表回调
|
// 接收列表回调
|
||||||
const resolveList = (result: string, path: string, list: Array<SftpFile>) => {
|
const resolveList = (result: string, path: string, list: Array<SftpFile>) => {
|
||||||
setTableLoading(false);
|
setTableLoading(false);
|
||||||
@@ -240,6 +252,7 @@
|
|||||||
session.value = await sessionManager.openSftp(props.tab, {
|
session.value = await sessionManager.openSftp(props.tab, {
|
||||||
setLoading: setTableLoading,
|
setLoading: setTableLoading,
|
||||||
connectCallback,
|
connectCallback,
|
||||||
|
onClose,
|
||||||
resolveList,
|
resolveList,
|
||||||
resolveSftpMkdir: resolveFileAction,
|
resolveSftpMkdir: resolveFileAction,
|
||||||
resolveSftpTouch: resolveFileAction,
|
resolveSftpTouch: resolveFileAction,
|
||||||
|
|||||||
@@ -81,9 +81,9 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理关闭消息
|
// 处理关闭消息
|
||||||
processClose({ sessionId, msg }: OutputPayload): void {
|
processClose({ sessionId, msg, forceClose }: OutputPayload): void {
|
||||||
const session = this.sessionManager.getSession(sessionId);
|
const session = this.sessionManager.getSession(sessionId);
|
||||||
// 无需处理 (直接关闭 tab )
|
// 无需处理 (直接关闭 tab)
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -98,6 +98,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
|||||||
} else if (session instanceof SftpSession) {
|
} else if (session instanceof SftpSession) {
|
||||||
// sftp 设置状态
|
// sftp 设置状态
|
||||||
session.connected = false;
|
session.connected = false;
|
||||||
|
session.resolver?.onClose(forceClose, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export const OutputProtocol = {
|
|||||||
// 主机连接关闭
|
// 主机连接关闭
|
||||||
CLOSE: {
|
CLOSE: {
|
||||||
type: 'cl',
|
type: 'cl',
|
||||||
template: ['type', 'sessionId', 'msg'],
|
template: ['type', 'sessionId', 'forceClose', 'msg'],
|
||||||
processMethod: 'processClose'
|
processMethod: 'processClose'
|
||||||
},
|
},
|
||||||
// pong
|
// pong
|
||||||
|
|||||||
@@ -347,6 +347,8 @@ export interface ISftpSessionResolver {
|
|||||||
setLoading: (loading: boolean) => void;
|
setLoading: (loading: boolean) => void;
|
||||||
// 连接后回调
|
// 连接后回调
|
||||||
connectCallback: () => void;
|
connectCallback: () => void;
|
||||||
|
// 关闭回调
|
||||||
|
onClose: (forceClose: string, msg: string) => void;
|
||||||
// 接受文件列表响应
|
// 接受文件列表响应
|
||||||
resolveList: (result: string, path: string, list: Array<SftpFile>) => void;
|
resolveList: (result: string, path: string, list: Array<SftpFile>) => void;
|
||||||
// 接收创建文件夹响应
|
// 接收创建文件夹响应
|
||||||
|
|||||||
@@ -57,7 +57,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- table -->
|
<!-- table -->
|
||||||
<a-table row-key="id"
|
<a-table row-key="id"
|
||||||
class="table-wrapper-8"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- table -->
|
<!-- table -->
|
||||||
<a-table row-key="id"
|
<a-table row-key="id"
|
||||||
class="table-wrapper-8"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
:allow-search="true"
|
:allow-search="true"
|
||||||
:filter-option="labelFilter"
|
:filter-option="labelFilter"
|
||||||
placeholder="请选择操作模块"
|
placeholder="请选择操作模块"
|
||||||
@change="m => selectedModule(m as string)"
|
|
||||||
allow-clear />
|
allow-clear />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<!-- 操作类型 -->
|
<!-- 操作类型 -->
|
||||||
@@ -81,7 +80,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
|
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
|
||||||
import type { OperatorLogQueryRequest } from '@/api/user/operator-log';
|
import type { OperatorLogQueryRequest } from '@/api/user/operator-log';
|
||||||
import { ref } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import useLoading from '@/hooks/loading';
|
import useLoading from '@/hooks/loading';
|
||||||
import useVisible from '@/hooks/visible';
|
import useVisible from '@/hooks/visible';
|
||||||
import { getOperatorLogCount, clearOperatorLog } from '@/api/user/operator-log';
|
import { getOperatorLogCount, clearOperatorLog } from '@/api/user/operator-log';
|
||||||
@@ -123,8 +122,8 @@
|
|||||||
|
|
||||||
defineExpose({ open });
|
defineExpose({ open });
|
||||||
|
|
||||||
// 选择类型
|
// 监听类型变化
|
||||||
const selectedModule = (module: string) => {
|
watch(() => formModel.value.module, (module: string | undefined) => {
|
||||||
if (!module) {
|
if (!module) {
|
||||||
// 不选择则重置 options
|
// 不选择则重置 options
|
||||||
typeOptions.value = toOptions(operatorLogTypeKey);
|
typeOptions.value = toOptions(operatorLogTypeKey);
|
||||||
@@ -138,7 +137,7 @@
|
|||||||
if (formModel.value.type && !formModel.value.type.startsWith(modulePrefix)) {
|
if (formModel.value.type && !formModel.value.type.startsWith(modulePrefix)) {
|
||||||
formModel.value.type = undefined;
|
formModel.value.type = undefined;
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
// 确定
|
// 确定
|
||||||
const handlerOk = async () => {
|
const handlerOk = async () => {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
:allow-search="true"
|
:allow-search="true"
|
||||||
:filter-option="labelFilter"
|
:filter-option="labelFilter"
|
||||||
placeholder="请选择操作模块"
|
placeholder="请选择操作模块"
|
||||||
@change="m => selectedModule(m as string)"
|
|
||||||
allow-clear />
|
allow-clear />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<!-- 操作类型 -->
|
<!-- 操作类型 -->
|
||||||
@@ -67,12 +66,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { OperatorLogQueryRequest } from '@/api/user/operator-log';
|
import type { OperatorLogQueryRequest } from '@/api/user/operator-log';
|
||||||
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
|
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref, watch } from 'vue';
|
||||||
import useLoading from '@/hooks/loading';
|
import useLoading from '@/hooks/loading';
|
||||||
import { useDictStore } from '@/store';
|
import { useDictStore } from '@/store';
|
||||||
import UserSelector from '@/components/user/user/user-selector.vue';
|
|
||||||
import { operatorLogModuleKey, operatorLogTypeKey, operatorRiskLevelKey, operatorLogResultKey } from '../types/const';
|
import { operatorLogModuleKey, operatorLogTypeKey, operatorRiskLevelKey, operatorLogResultKey } from '../types/const';
|
||||||
import { labelFilter } from '@/types/form';
|
import { labelFilter } from '@/types/form';
|
||||||
|
import UserSelector from '@/components/user/user/user-selector.vue';
|
||||||
|
|
||||||
const emits = defineEmits(['submit']);
|
const emits = defineEmits(['submit']);
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -94,8 +93,8 @@
|
|||||||
startTimeRange: undefined,
|
startTimeRange: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 选择类型
|
// 监听类型变化
|
||||||
const selectedModule = (module: string) => {
|
watch(() => formModel.module, (module: string | undefined) => {
|
||||||
if (!module) {
|
if (!module) {
|
||||||
// 不选择则重置 options
|
// 不选择则重置 options
|
||||||
typeOptions.value = toOptions(operatorLogTypeKey);
|
typeOptions.value = toOptions(operatorLogTypeKey);
|
||||||
@@ -109,7 +108,7 @@
|
|||||||
if (formModel.type && !formModel.type.startsWith(modulePrefix)) {
|
if (formModel.type && !formModel.type.startsWith(modulePrefix)) {
|
||||||
formModel.type = undefined;
|
formModel.type = undefined;
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
// 切换页码
|
// 切换页码
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 表格 -->
|
<!-- 表格 -->
|
||||||
<a-table row-key="id"
|
<a-table row-key="id"
|
||||||
class="table-wrapper-8"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:columns="tableColumns"
|
:columns="tableColumns"
|
||||||
@@ -33,6 +32,19 @@
|
|||||||
<icon-copy class="copy-left" @click="copy(record.originLogInfo, '已复制')" />
|
<icon-copy class="copy-left" @click="copy(record.originLogInfo, '已复制')" />
|
||||||
<span v-html="replaceHtmlTag(record.logInfo)" />
|
<span v-html="replaceHtmlTag(record.logInfo)" />
|
||||||
</template>
|
</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">
|
||||||
|
{{ record.address }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<template #handle="{ record }">
|
<template #handle="{ record }">
|
||||||
<div class="table-handle-wrapper">
|
<div class="table-handle-wrapper">
|
||||||
@@ -144,7 +156,7 @@
|
|||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let cols = [...columns].map(s => {
|
let cols = columns.map(s => {
|
||||||
return { ...s };
|
return { ...s };
|
||||||
}).filter(s => s.dataIndex !== 'username');
|
}).filter(s => s.dataIndex !== 'username');
|
||||||
if (props.handleColumn) {
|
if (props.handleColumn) {
|
||||||
@@ -164,4 +176,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
.operator-location {
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-address {
|
||||||
|
margin-top: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -46,7 +46,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- 表格 -->
|
<!-- 表格 -->
|
||||||
<a-table row-key="id"
|
<a-table row-key="id"
|
||||||
class="table-wrapper-8"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
@@ -80,6 +79,19 @@
|
|||||||
<icon-copy class="copy-left" @click="copy(record.originLogInfo, '已复制')" />
|
<icon-copy class="copy-left" @click="copy(record.originLogInfo, '已复制')" />
|
||||||
<span v-html="replaceHtmlTag(record.logInfo)" />
|
<span v-html="replaceHtmlTag(record.logInfo)" />
|
||||||
</template>
|
</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">
|
||||||
|
{{ record.address }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<template #handle="{ record }">
|
<template #handle="{ record }">
|
||||||
<div class="table-handle-wrapper">
|
<div class="table-handle-wrapper">
|
||||||
@@ -220,5 +232,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
.operator-location {
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator-address {
|
||||||
|
margin-top: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const getLogDetail = (record: OperatorLogQueryResponse): Record<string, a
|
|||||||
detail.duration = `${record.duration} ms`;
|
detail.duration = `${record.duration} ms`;
|
||||||
detail.startTime = dateFormat(new Date(record.startTime));
|
detail.startTime = dateFormat(new Date(record.startTime));
|
||||||
detail.endTime = dateFormat(new Date(record.endTime));
|
detail.endTime = dateFormat(new Date(record.endTime));
|
||||||
detail.extra = record?.extra;
|
detail.extra = JSON.parse(record?.extra);
|
||||||
detail.returnValue = JSON.parse(record?.returnValue);
|
detail.returnValue = JSON.parse(record?.returnValue);
|
||||||
return detail;
|
return detail;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -32,6 +32,13 @@ const columns = [
|
|||||||
slotName: 'originLogInfo',
|
slotName: 'originLogInfo',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true,
|
tooltip: true,
|
||||||
|
}, {
|
||||||
|
title: '操作地址',
|
||||||
|
dataIndex: 'address',
|
||||||
|
slotName: 'address',
|
||||||
|
width: 156,
|
||||||
|
align: 'left',
|
||||||
|
ellipsis: true,
|
||||||
}, {
|
}, {
|
||||||
title: '操作时间',
|
title: '操作时间',
|
||||||
dataIndex: 'createTime',
|
dataIndex: 'createTime',
|
||||||
|
|||||||
@@ -50,7 +50,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- table -->
|
<!-- table -->
|
||||||
<a-table row-key="id"
|
<a-table row-key="id"
|
||||||
class="table-wrapper-8"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- table -->
|
<!-- table -->
|
||||||
<a-table row-key="id"
|
<a-table row-key="id"
|
||||||
class="table-wrapper-8"
|
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|||||||
Reference in New Issue
Block a user