@@ -1,6 +1,6 @@
|
||||
version: '3.3'
|
||||
|
||||
# latest = 2.4.0
|
||||
# latest = 2.4.1
|
||||
services:
|
||||
ui:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-ui:latest
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#/bin/bash
|
||||
set -e
|
||||
version=2.4.0
|
||||
version=2.4.1
|
||||
docker build -t orion-visor-adminer:${version} .
|
||||
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:${version}
|
||||
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:latest
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM guacamole/guacd:1.5.5
|
||||
FROM guacamole/guacd:1.6.0
|
||||
USER root
|
||||
# 系统时区
|
||||
ARG TZ=Asia/Shanghai
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#/bin/bash
|
||||
set -e
|
||||
version=2.4.0
|
||||
version=2.4.1
|
||||
docker build -t orion-visor-guacd:${version} .
|
||||
docker tag orion-visor-guacd:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-guacd:${version}
|
||||
docker tag orion-visor-guacd:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-guacd:latest
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#/bin/bash
|
||||
set -e
|
||||
version=2.4.0
|
||||
version=2.4.1
|
||||
cp -r ../../sql ./sql
|
||||
docker build -t orion-visor-mysql:${version} .
|
||||
rm -rf ./sql
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#/bin/bash
|
||||
set -e
|
||||
version=2.4.0
|
||||
version=2.4.1
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:${version}
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:${version}
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:${version}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#/bin/bash
|
||||
set -e
|
||||
version=2.4.0
|
||||
version=2.4.1
|
||||
docker build -t orion-visor-redis:${version} .
|
||||
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:${version}
|
||||
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:latest
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#/bin/bash
|
||||
set -e
|
||||
version=2.4.0
|
||||
version=2.4.1
|
||||
mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar
|
||||
docker build -t orion-visor-service:${version} .
|
||||
rm -rf ./orion-visor-launch.jar
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#/bin/bash
|
||||
set -e
|
||||
version=2.4.0
|
||||
version=2.4.1
|
||||
mv ../../orion-visor-ui/dist ./dist
|
||||
docker build -t orion-visor-ui:${version} .
|
||||
rm -rf ./orion-visor-launch.jar
|
||||
|
||||
@@ -36,7 +36,7 @@ public interface AppConst extends OrionConst {
|
||||
/**
|
||||
* 同 ${orion.version} 迭代时候需要手动更改
|
||||
*/
|
||||
String VERSION = "2.4.0";
|
||||
String VERSION = "2.4.1";
|
||||
|
||||
/**
|
||||
* 同 ${spring.application.name}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.common.constant;
|
||||
|
||||
import cn.orionsec.kit.lang.constant.StandardHttpHeader;
|
||||
|
||||
/**
|
||||
* http 请求头
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2025/7/1 1:02
|
||||
*/
|
||||
public interface HttpHeaderConst extends StandardHttpHeader {
|
||||
|
||||
String APP_VERSION = "X-App-Version";
|
||||
|
||||
}
|
||||
@@ -47,6 +47,9 @@ public class RdpConnectConfig extends BaseConnectConfig {
|
||||
@Schema(description = "低带宽模式")
|
||||
private Boolean lowBandwidthMode;
|
||||
|
||||
@Schema(description = "初始化程序")
|
||||
private String initialProgram;
|
||||
|
||||
@Schema(description = "RDP 版本是否大于8.1")
|
||||
private Boolean versionGt81;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<url>https://github.com/dromara/orion-visor</url>
|
||||
|
||||
<properties>
|
||||
<revision>2.4.0</revision>
|
||||
<revision>2.4.1</revision>
|
||||
<spring.boot.version>2.7.17</spring.boot.version>
|
||||
<spring.boot.admin.version>2.7.15</spring.boot.admin.version>
|
||||
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>
|
||||
@@ -34,7 +34,7 @@
|
||||
<mockito.inline.version>4.11.0</mockito.inline.version>
|
||||
<jedis.mock.version>1.0.7</jedis.mock.version>
|
||||
<podam.version>7.2.11.RELEASE</podam.version>
|
||||
<guacd.version>1.5.5</guacd.version>
|
||||
<guacd.version>1.6.0</guacd.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
@@ -39,9 +39,9 @@ import java.util.function.Function;
|
||||
*/
|
||||
public class ReplaceVersion {
|
||||
|
||||
private static final String TARGET_VERSION = "2.3.9";
|
||||
private static final String TARGET_VERSION = "2.4.0";
|
||||
|
||||
private static final String REPLACE_VERSION = "2.4.0";
|
||||
private static final String REPLACE_VERSION = "2.4.1";
|
||||
|
||||
private static final String PATH = new File("").getAbsolutePath();
|
||||
|
||||
|
||||
@@ -56,4 +56,9 @@ public class HostRdpExtraModel implements GenericsDataModel {
|
||||
*/
|
||||
private Boolean lowBandwidthMode;
|
||||
|
||||
/**
|
||||
* 初始化程序
|
||||
*/
|
||||
private String initialProgram;
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,14 @@ import java.util.List;
|
||||
*/
|
||||
public interface HostConfigService {
|
||||
|
||||
/**
|
||||
* 初始化主机配置
|
||||
*
|
||||
* @param hostId hostId
|
||||
* @param types types
|
||||
*/
|
||||
void initHostConfig(Long hostId, List<String> types);
|
||||
|
||||
/**
|
||||
* 更新主机配置
|
||||
*
|
||||
|
||||
@@ -67,6 +67,34 @@ public class HostConfigServiceImpl implements HostConfigService {
|
||||
@Resource
|
||||
private HostConfigDAO hostConfigDAO;
|
||||
|
||||
@Override
|
||||
public void initHostConfig(Long hostId, List<String> types) {
|
||||
// 查询主机配置类型
|
||||
List<String> hostConfigTypes = hostConfigDAO.selectByHostId(hostId)
|
||||
.stream()
|
||||
.map(HostConfigDO::getType)
|
||||
.collect(Collectors.toList());
|
||||
List<HostConfigDO> configs = new ArrayList<>();
|
||||
for (String type : types) {
|
||||
// 配置存在则跳过
|
||||
if (hostConfigTypes.contains(type)) {
|
||||
continue;
|
||||
}
|
||||
// 配置不存在则初始化
|
||||
HostConfigDO config = HostConfigDO.builder()
|
||||
.hostId(hostId)
|
||||
.type(type)
|
||||
.status(EnableStatus.ENABLED.name())
|
||||
.config(HostConfigStrategyEnum.of(type).getDefault().serial())
|
||||
.build();
|
||||
configs.add(config);
|
||||
}
|
||||
// 插入主机配置
|
||||
if (!configs.isEmpty()) {
|
||||
hostConfigDAO.insertBatch(configs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer updateHostConfig(HostConfigUpdateRequest request) {
|
||||
log.info("HostConfigService-updateHostConfig request: {}", request);
|
||||
|
||||
@@ -293,8 +293,9 @@ public class HostConnectServiceImpl implements HostConnectService {
|
||||
// 填充基础主机信息
|
||||
this.setBaseConnectConfig(connectConfig, host);
|
||||
if (extra != null) {
|
||||
// 设置低带宽模式
|
||||
// 设置额外配置信息
|
||||
connectConfig.setLowBandwidthMode(extra.getLowBandwidthMode());
|
||||
connectConfig.setInitialProgram(extra.getInitialProgram());
|
||||
// 获取自定义认证方式
|
||||
HostExtraAuthTypeEnum extraAuthType = HostExtraAuthTypeEnum.of(extra.getAuthType());
|
||||
if (HostExtraAuthTypeEnum.CUSTOM_IDENTITY.equals(extraAuthType)) {
|
||||
|
||||
@@ -128,8 +128,10 @@ public class HostServiceImpl implements HostService {
|
||||
this.checkHostCodePresent(record);
|
||||
// 插入主机
|
||||
int effect = hostDAO.insert(record);
|
||||
log.info("HostService-createHost effect: {}", effect);
|
||||
Long id = record.getId();
|
||||
log.info("HostService-createHost id: {}, effect: {}", id, effect);
|
||||
// 初始化主机配置
|
||||
hostConfigService.initHostConfig(id, request.getTypes());
|
||||
// 插入 tag
|
||||
tagRelApi.addTagRel(TagTypeEnum.HOST, id, request.getTags());
|
||||
// 引用分组
|
||||
@@ -183,6 +185,8 @@ public class HostServiceImpl implements HostService {
|
||||
// 修改 config 状态
|
||||
hostConfigDAO.updateConfigStatus(id, types, EnableStatus.ENABLED.name());
|
||||
hostConfigDAO.updateConfigInvertStatus(id, types, EnableStatus.DISABLED.name());
|
||||
// 初始化主机配置
|
||||
hostConfigService.initHostConfig(id, types);
|
||||
// 删除缓存
|
||||
this.clearCache();
|
||||
return effect;
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
*/
|
||||
package org.dromara.visor.module.common.utils;
|
||||
|
||||
import cn.orionsec.kit.lang.constant.Letters;
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.io.Files1;
|
||||
import cn.orionsec.kit.net.host.sftp.SftpExecutor;
|
||||
import cn.orionsec.kit.net.host.sftp.SftpFile;
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
@@ -69,21 +67,4 @@ public class SftpUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取移动目标路径
|
||||
*
|
||||
* @param source source
|
||||
* @param target target
|
||||
* @return absolute target
|
||||
*/
|
||||
public static String getAbsoluteTargetPath(String source, String target) {
|
||||
if (target.charAt(0) == Letters.SLASH) {
|
||||
// 绝对路径
|
||||
return Files1.getPath(Files1.normalize(target));
|
||||
} else {
|
||||
// 相对路径
|
||||
return Files1.getPath(Files1.normalize(Files1.getPath(source + "/../" + target)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.annotation.OperatorLog;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.enums.ReturnType;
|
||||
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
|
||||
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
|
||||
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
|
||||
@@ -58,7 +59,7 @@ public class AuthenticationController {
|
||||
@Resource
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
@OperatorLog(AuthenticationOperatorType.LOGIN)
|
||||
@OperatorLog(value = AuthenticationOperatorType.LOGIN, ret = ReturnType.IGNORE)
|
||||
@PermitAll
|
||||
@Operation(summary = "登录")
|
||||
@PostMapping("/login")
|
||||
|
||||
@@ -25,6 +25,8 @@ package org.dromara.visor.module.infra.controller;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.AppConst;
|
||||
import org.dromara.visor.common.constant.HttpHeaderConst;
|
||||
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
|
||||
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
|
||||
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
|
||||
@@ -37,6 +39,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -67,7 +70,12 @@ public class UserAggregateController {
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@GetMapping("/user")
|
||||
@Operation(summary = "获取用户权限聚合信息")
|
||||
public UserAggregateVO getUserAggregateInfo() {
|
||||
public UserAggregateVO getUserAggregateInfo(HttpServletResponse response) {
|
||||
// FIXME KIT
|
||||
// 设置版本号请求头
|
||||
response.setHeader(HttpHeaderConst.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaderConst.APP_VERSION);
|
||||
response.setHeader(HttpHeaderConst.APP_VERSION, AppConst.VERSION);
|
||||
// 获取用户信息
|
||||
return userAggregateService.getUserAggregateInfo();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit;
|
||||
public interface PreferenceCacheKeyDefine {
|
||||
|
||||
CacheKeyDefine PREFERENCE = new CacheKeyBuilder()
|
||||
.key("user:prefer:{}:{}")
|
||||
.key("v1:user:prefer:{}:{}")
|
||||
.desc("用户偏好 ${userId} ${type}")
|
||||
.type(JSONObject.class)
|
||||
.struct(RedisCacheStruct.STRING)
|
||||
|
||||
@@ -49,7 +49,4 @@ public class AppInfoVO implements Serializable {
|
||||
@Schema(description = "系统版本")
|
||||
private String version;
|
||||
|
||||
@Schema(description = "机器码")
|
||||
private String uuid;
|
||||
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ public class TerminalPreferenceModel implements GenericsDataModel {
|
||||
private String newConnectionType;
|
||||
|
||||
/**
|
||||
* 终端主题
|
||||
* ssh 主题
|
||||
*/
|
||||
private JSONObject theme;
|
||||
private JSONObject sshTheme;
|
||||
|
||||
/**
|
||||
* ssh 显示设置
|
||||
@@ -61,40 +61,40 @@ public class TerminalPreferenceModel implements GenericsDataModel {
|
||||
private JSONObject sshDisplaySetting;
|
||||
|
||||
/**
|
||||
* rdp 图形化设置
|
||||
* ssh 右键菜单设置
|
||||
*/
|
||||
private JSONObject rdpGraphSetting;
|
||||
private List<String> sshRightMenuSetting;
|
||||
|
||||
/**
|
||||
* ssh 操作栏设置
|
||||
*/
|
||||
private JSONObject sshActionBarSetting;
|
||||
|
||||
/**
|
||||
* ssh 交互设置
|
||||
*/
|
||||
private JSONObject sshInteractSetting;
|
||||
|
||||
/**
|
||||
* ssh 插件设置
|
||||
*/
|
||||
private JSONObject sshPluginsSetting;
|
||||
|
||||
/**
|
||||
* rdp 会话设置
|
||||
*/
|
||||
private JSONObject rdpSessionSetting;
|
||||
|
||||
/**
|
||||
* rdp 图形化设置
|
||||
*/
|
||||
private JSONObject rdpGraphSetting;
|
||||
|
||||
/**
|
||||
* rdp 操作栏设置
|
||||
*/
|
||||
private JSONObject rdpActionBarSetting;
|
||||
|
||||
/**
|
||||
* 右键菜单设置
|
||||
*/
|
||||
private List<String> rightMenuSetting;
|
||||
|
||||
/**
|
||||
* 交互设置
|
||||
*/
|
||||
private JSONObject interactSetting;
|
||||
|
||||
/**
|
||||
* 插件设置
|
||||
*/
|
||||
private JSONObject pluginsSetting;
|
||||
|
||||
/**
|
||||
* 会话设置
|
||||
*/
|
||||
private JSONObject sessionSetting;
|
||||
|
||||
/**
|
||||
* 快捷键设置
|
||||
*/
|
||||
@@ -148,94 +148,6 @@ public class TerminalPreferenceModel implements GenericsDataModel {
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class RdpGraphSettingModel implements IJsonObject {
|
||||
|
||||
/**
|
||||
* 显示大小
|
||||
*/
|
||||
private String displaySize;
|
||||
|
||||
/**
|
||||
* 显示宽度
|
||||
*/
|
||||
private Integer displayWidth;
|
||||
|
||||
/**
|
||||
* 显示高度
|
||||
*/
|
||||
private Integer displayHeight;
|
||||
|
||||
/**
|
||||
* 启用音频输入
|
||||
*/
|
||||
private Boolean enableAudioInput;
|
||||
|
||||
/**
|
||||
* 启用音频输出
|
||||
*/
|
||||
private Boolean enableAudioOutput;
|
||||
|
||||
/**
|
||||
* 颜色深度
|
||||
*/
|
||||
private Integer colorDepth;
|
||||
|
||||
/**
|
||||
* 无损压缩
|
||||
*/
|
||||
private Boolean forceLossless;
|
||||
|
||||
/**
|
||||
* 启用壁纸
|
||||
*/
|
||||
private Boolean enableWallpaper;
|
||||
|
||||
/**
|
||||
* 启用主题
|
||||
*/
|
||||
private Boolean enableTheming;
|
||||
|
||||
/**
|
||||
* 启动平滑字体
|
||||
*/
|
||||
private Boolean enableFontSmoothing;
|
||||
|
||||
/**
|
||||
* 启用窗口拖动
|
||||
*/
|
||||
private Boolean enableFullWindowDrag;
|
||||
|
||||
/**
|
||||
* 启用桌面合成
|
||||
*/
|
||||
private Boolean enableDesktopComposition;
|
||||
|
||||
/**
|
||||
* 启用菜单动画
|
||||
*/
|
||||
private Boolean enableMenuAnimations;
|
||||
|
||||
/**
|
||||
* 禁用位图缓存
|
||||
*/
|
||||
private Boolean disableBitmapCaching;
|
||||
|
||||
/**
|
||||
* 禁用离屏缓存
|
||||
*/
|
||||
private Boolean disableOffscreenCaching;
|
||||
|
||||
/**
|
||||
* 禁用字形缓存
|
||||
*/
|
||||
private Boolean disableGlyphCaching;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@@ -323,55 +235,7 @@ public class TerminalPreferenceModel implements GenericsDataModel {
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class RdpActionBarSettingModel implements IJsonObject {
|
||||
|
||||
/**
|
||||
* 位置
|
||||
*/
|
||||
private String position;
|
||||
|
||||
/**
|
||||
* 显示设置
|
||||
*/
|
||||
private Boolean display;
|
||||
|
||||
/**
|
||||
* 组合键
|
||||
*/
|
||||
private Boolean combinationKey;
|
||||
|
||||
/**
|
||||
* 剪切板
|
||||
*/
|
||||
private Boolean clipboard;
|
||||
|
||||
/**
|
||||
* 上传
|
||||
*/
|
||||
private Boolean upload;
|
||||
|
||||
/**
|
||||
* 保存为 rdp 文件
|
||||
*/
|
||||
private Boolean saveRdp;
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
private Boolean disconnect;
|
||||
|
||||
/**
|
||||
* 关闭
|
||||
*/
|
||||
private Boolean close;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class InteractSettingModel implements IJsonObject {
|
||||
public static class SshInteractSettingModel implements IJsonObject {
|
||||
|
||||
/**
|
||||
* 快速滚动
|
||||
@@ -423,13 +287,23 @@ public class TerminalPreferenceModel implements GenericsDataModel {
|
||||
*/
|
||||
private String wordSeparator;
|
||||
|
||||
/**
|
||||
* 伪终端类型
|
||||
*/
|
||||
private String terminalEmulationType;
|
||||
|
||||
/**
|
||||
* 保存在缓冲区的行数
|
||||
*/
|
||||
private Integer scrollBackLine;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class PluginsSettingModel implements IJsonObject {
|
||||
public static class SshPluginsSettingModel implements IJsonObject {
|
||||
|
||||
/**
|
||||
* 超链接插件
|
||||
@@ -457,17 +331,153 @@ public class TerminalPreferenceModel implements GenericsDataModel {
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class SessionSettingModel implements IJsonObject {
|
||||
public static class RdpGraphSettingModel implements IJsonObject {
|
||||
|
||||
/**
|
||||
* 伪终端类型
|
||||
* 显示大小
|
||||
*/
|
||||
private String terminalEmulationType;
|
||||
private String displaySize;
|
||||
|
||||
/**
|
||||
* 保存在缓冲区的行数
|
||||
* 显示宽度
|
||||
*/
|
||||
private Integer scrollBackLine;
|
||||
private Integer displayWidth;
|
||||
|
||||
/**
|
||||
* 显示高度
|
||||
*/
|
||||
private Integer displayHeight;
|
||||
|
||||
/**
|
||||
* 颜色深度
|
||||
*/
|
||||
private Integer colorDepth;
|
||||
|
||||
/**
|
||||
* 无损压缩
|
||||
*/
|
||||
private Boolean forceLossless;
|
||||
|
||||
/**
|
||||
* 启用壁纸
|
||||
*/
|
||||
private Boolean enableWallpaper;
|
||||
|
||||
/**
|
||||
* 启用主题
|
||||
*/
|
||||
private Boolean enableTheming;
|
||||
|
||||
/**
|
||||
* 启动平滑字体
|
||||
*/
|
||||
private Boolean enableFontSmoothing;
|
||||
|
||||
/**
|
||||
* 启用窗口拖动
|
||||
*/
|
||||
private Boolean enableFullWindowDrag;
|
||||
|
||||
/**
|
||||
* 启用桌面合成
|
||||
*/
|
||||
private Boolean enableDesktopComposition;
|
||||
|
||||
/**
|
||||
* 启用菜单动画
|
||||
*/
|
||||
private Boolean enableMenuAnimations;
|
||||
|
||||
/**
|
||||
* 禁用位图缓存
|
||||
*/
|
||||
private Boolean disableBitmapCaching;
|
||||
|
||||
/**
|
||||
* 禁用离屏缓存
|
||||
*/
|
||||
private Boolean disableOffscreenCaching;
|
||||
|
||||
/**
|
||||
* 禁用字形缓存
|
||||
*/
|
||||
private Boolean disableGlyphCaching;
|
||||
|
||||
/**
|
||||
* 禁用图形加速
|
||||
*/
|
||||
private Boolean disableGfx;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class RdpActionBarSettingModel implements IJsonObject {
|
||||
|
||||
/**
|
||||
* 位置
|
||||
*/
|
||||
private String position;
|
||||
|
||||
/**
|
||||
* 显示设置
|
||||
*/
|
||||
private Boolean display;
|
||||
|
||||
/**
|
||||
* 组合键
|
||||
*/
|
||||
private Boolean combinationKey;
|
||||
|
||||
/**
|
||||
* 剪切板
|
||||
*/
|
||||
private Boolean clipboard;
|
||||
|
||||
/**
|
||||
* 上传
|
||||
*/
|
||||
private Boolean upload;
|
||||
|
||||
/**
|
||||
* 保存为 rdp 文件
|
||||
*/
|
||||
private Boolean saveRdp;
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
private Boolean disconnect;
|
||||
|
||||
/**
|
||||
* 关闭
|
||||
*/
|
||||
private Boolean close;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class RdpSessionSettingModel implements IJsonObject {
|
||||
|
||||
/**
|
||||
* 启用音频输入
|
||||
*/
|
||||
private Boolean enableAudioInput;
|
||||
|
||||
/**
|
||||
* 启用音频输出
|
||||
*/
|
||||
private Boolean enableAudioOutput;
|
||||
|
||||
/**
|
||||
* 驱动挂载模式
|
||||
*/
|
||||
private String driveMountMode;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -51,23 +51,23 @@ public class TerminalPreferenceStrategy extends AbstractGenericsDataStrategy<Ter
|
||||
// 连接类型
|
||||
.newConnectionType("group")
|
||||
// ssh 主题
|
||||
.theme(new JSONObject())
|
||||
.sshTheme(new JSONObject())
|
||||
// ssh 显示设置
|
||||
.sshDisplaySetting(JSONObject.parseObject(this.getDefaultSshDisplaySetting()))
|
||||
// rdp 图形化设置
|
||||
.rdpGraphSetting(JSONObject.parseObject(this.getDefaultRdpGraphSetting()))
|
||||
// ssh 操作栏设置
|
||||
.sshActionBarSetting(JSONObject.parseObject(this.getDefaultSshActionBarSetting()))
|
||||
// ssh 右键菜单设置
|
||||
.sshRightMenuSetting(this.getDefaultSshRightMenuSetting())
|
||||
// ssh 交互设置
|
||||
.sshInteractSetting(JSONObject.parseObject(this.getDefaultSshInteractSetting()))
|
||||
// ssh 插件设置
|
||||
.sshPluginsSetting(JSONObject.parseObject(this.getDefaultSshPluginsSetting()))
|
||||
// rdp 图形化设置
|
||||
.rdpGraphSetting(JSONObject.parseObject(this.getDefaultRdpGraphSetting()))
|
||||
// rdp 操作栏设置
|
||||
.rdpActionBarSetting(JSONObject.parseObject(this.getDefaultRdpActionBarSetting()))
|
||||
// ssh 右键菜单设置
|
||||
.rightMenuSetting(this.getDefaultRightMenuSetting())
|
||||
// 交互设置
|
||||
.interactSetting(JSONObject.parseObject(this.getDefaultInteractSetting()))
|
||||
// 插件设置
|
||||
.pluginsSetting(JSONObject.parseObject(this.getDefaultPluginsSetting()))
|
||||
// 会话设置
|
||||
.sessionSetting(JSONObject.parseObject(this.getDefaultSessionSetting()))
|
||||
// rdp 会话设置
|
||||
.rdpSessionSetting(JSONObject.parseObject(this.getDefaultRdpSessionSetting()))
|
||||
// 快捷键设置
|
||||
.shortcutSetting(JSONObject.parseObject(this.getDefaultShortcutSetting()))
|
||||
.build();
|
||||
@@ -94,30 +94,12 @@ public class TerminalPreferenceStrategy extends AbstractGenericsDataStrategy<Ter
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 rdp 图形化默认设置
|
||||
* 获取 ssh 右键菜单默认设置
|
||||
*
|
||||
* @return setting
|
||||
*/
|
||||
private String getDefaultRdpGraphSetting() {
|
||||
return TerminalPreferenceModel.RdpGraphSettingModel.builder()
|
||||
.displaySize("fit")
|
||||
.displayWidth(0)
|
||||
.displayHeight(0)
|
||||
.colorDepth(24)
|
||||
.enableAudioInput(false)
|
||||
.enableAudioOutput(true)
|
||||
.forceLossless(true)
|
||||
.enableWallpaper(true)
|
||||
.enableTheming(true)
|
||||
.enableFontSmoothing(true)
|
||||
.enableFullWindowDrag(true)
|
||||
.enableDesktopComposition(true)
|
||||
.enableMenuAnimations(false)
|
||||
.disableBitmapCaching(false)
|
||||
.disableOffscreenCaching(false)
|
||||
.disableGlyphCaching(false)
|
||||
.build()
|
||||
.toJsonString();
|
||||
private List<String> getDefaultSshRightMenuSetting() {
|
||||
return Lists.of("selectAll", "copy", "paste", "search", "clear");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,6 +129,70 @@ public class TerminalPreferenceStrategy extends AbstractGenericsDataStrategy<Ter
|
||||
.toJsonString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ssh 默认交互设置
|
||||
*
|
||||
* @return setting
|
||||
*/
|
||||
private String getDefaultSshInteractSetting() {
|
||||
return TerminalPreferenceModel.SshInteractSettingModel.builder()
|
||||
.fastScrollModifier(true)
|
||||
.altClickMovesCursor(true)
|
||||
.rightClickSelectsWord(false)
|
||||
.selectionChangeCopy(false)
|
||||
.copyAutoTrim(false)
|
||||
.pasteAutoTrim(false)
|
||||
.rightClickPaste(false)
|
||||
.enableRightClickMenu(true)
|
||||
.enableBell(false)
|
||||
.wordSeparator("/\\()\"'` -.,:;<>~!@#$%^&*|+=[]{}~?│")
|
||||
.terminalEmulationType(TerminalType.XTERM.getType())
|
||||
.scrollBackLine(1000)
|
||||
.build()
|
||||
.toJsonString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认插件设置
|
||||
*
|
||||
* @return setting
|
||||
*/
|
||||
private String getDefaultSshPluginsSetting() {
|
||||
return TerminalPreferenceModel.SshPluginsSettingModel.builder()
|
||||
.enableWeblinkPlugin(true)
|
||||
.enableWebglPlugin(true)
|
||||
.enableUnicodePlugin(true)
|
||||
.enableImagePlugin(false)
|
||||
.build()
|
||||
.toJsonString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 rdp 图形化默认设置
|
||||
*
|
||||
* @return setting
|
||||
*/
|
||||
private String getDefaultRdpGraphSetting() {
|
||||
return TerminalPreferenceModel.RdpGraphSettingModel.builder()
|
||||
.displaySize("fit")
|
||||
.displayWidth(0)
|
||||
.displayHeight(0)
|
||||
.colorDepth(24)
|
||||
.forceLossless(true)
|
||||
.enableWallpaper(true)
|
||||
.enableTheming(true)
|
||||
.enableFontSmoothing(true)
|
||||
.enableFullWindowDrag(true)
|
||||
.enableDesktopComposition(true)
|
||||
.enableMenuAnimations(false)
|
||||
.disableBitmapCaching(false)
|
||||
.disableOffscreenCaching(false)
|
||||
.disableGlyphCaching(false)
|
||||
.disableGfx(false)
|
||||
.build()
|
||||
.toJsonString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 rdp 操作栏默认设置
|
||||
*
|
||||
@@ -167,59 +213,15 @@ public class TerminalPreferenceStrategy extends AbstractGenericsDataStrategy<Ter
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ssh 右键菜单默认设置
|
||||
* 获取 rdp 默认会话设置
|
||||
*
|
||||
* @return setting
|
||||
*/
|
||||
private List<String> getDefaultRightMenuSetting() {
|
||||
return Lists.of("selectAll", "copy", "paste", "search", "clear");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认交互设置
|
||||
*
|
||||
* @return setting
|
||||
*/
|
||||
private String getDefaultInteractSetting() {
|
||||
return TerminalPreferenceModel.InteractSettingModel.builder()
|
||||
.fastScrollModifier(true)
|
||||
.altClickMovesCursor(true)
|
||||
.rightClickSelectsWord(false)
|
||||
.selectionChangeCopy(false)
|
||||
.copyAutoTrim(false)
|
||||
.pasteAutoTrim(false)
|
||||
.rightClickPaste(false)
|
||||
.enableRightClickMenu(true)
|
||||
.enableBell(false)
|
||||
.wordSeparator("/\\()\"'` -.,:;<>~!@#$%^&*|+=[]{}~?│")
|
||||
.build()
|
||||
.toJsonString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认插件设置
|
||||
*
|
||||
* @return setting
|
||||
*/
|
||||
private String getDefaultPluginsSetting() {
|
||||
return TerminalPreferenceModel.PluginsSettingModel.builder()
|
||||
.enableWeblinkPlugin(true)
|
||||
.enableWebglPlugin(true)
|
||||
.enableUnicodePlugin(true)
|
||||
.enableImagePlugin(false)
|
||||
.build()
|
||||
.toJsonString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认会话设置
|
||||
*
|
||||
* @return setting
|
||||
*/
|
||||
private String getDefaultSessionSetting() {
|
||||
return TerminalPreferenceModel.SessionSettingModel.builder()
|
||||
.terminalEmulationType(TerminalType.XTERM.getType())
|
||||
.scrollBackLine(1000)
|
||||
private String getDefaultRdpSessionSetting() {
|
||||
return TerminalPreferenceModel.RdpSessionSettingModel.builder()
|
||||
.enableAudioInput(false)
|
||||
.enableAudioOutput(true)
|
||||
.driveMountMode("ASSET")
|
||||
.build()
|
||||
.toJsonString();
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ import org.dromara.visor.module.infra.entity.request.system.SystemSettingUpdateR
|
||||
import org.dromara.visor.module.infra.entity.vo.AppInfoVO;
|
||||
import org.dromara.visor.module.infra.entity.vo.RsaKeyPairVO;
|
||||
import org.dromara.visor.module.infra.service.SystemSettingService;
|
||||
import org.dromara.visor.module.infra.utils.SystemUuidUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -73,7 +72,6 @@ public class SystemSettingServiceImpl implements SystemSettingService {
|
||||
public AppInfoVO getAppInfo() {
|
||||
return AppInfoVO.builder()
|
||||
.version(AppConst.VERSION)
|
||||
.uuid(SystemUuidUtils.getSystemUuid())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.infra.utils;
|
||||
|
||||
import cn.orionsec.kit.ext.process.ProcessAwaitExecutor;
|
||||
import cn.orionsec.kit.lang.support.Attempt;
|
||||
import cn.orionsec.kit.lang.utils.Arrays1;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.crypto.Signatures;
|
||||
import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
/**
|
||||
* 系统 UUID 工具类
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2025/1/16 11:07
|
||||
*/
|
||||
public class SystemUuidUtils {
|
||||
|
||||
private static String uuid;
|
||||
|
||||
private SystemUuidUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统 uuid
|
||||
*
|
||||
* @return uuid
|
||||
*/
|
||||
public static String getSystemUuid() {
|
||||
if (SystemUuidUtils.uuid != null) {
|
||||
return SystemUuidUtils.uuid;
|
||||
}
|
||||
String[][] cmd = new String[][]{
|
||||
new String[]{"/bin/sh", "-c", "cat /sys/class/dmi/id/product_serial"},
|
||||
new String[]{"/bin/bash", "-c", "cat /sys/class/dmi/id/product_serial"},
|
||||
new String[]{"/bin/sh", "-c", "dmidecode -s system-uuid"},
|
||||
new String[]{"/bin/bash", "-c", "dmidecode -s system-uuid"},
|
||||
new String[]{"cmd", "/c", "wmic csproduct get uuid"}
|
||||
};
|
||||
for (String[] s : cmd) {
|
||||
try {
|
||||
String uuid = SystemUuidUtils.getCommandOutput(s);
|
||||
if (Strings.isBlank(uuid)) {
|
||||
continue;
|
||||
}
|
||||
// 去除符号并且转为大写
|
||||
uuid = uuid.replaceAll(Const.DASHED, Const.EMPTY)
|
||||
.toUpperCase()
|
||||
.trim();
|
||||
// 去除 \n
|
||||
String extraUuid = Arrays1.last(uuid.trim().split(Const.LF));
|
||||
if (!Strings.isBlank(extraUuid)) {
|
||||
uuid = extraUuid.trim();
|
||||
}
|
||||
// 去除 :
|
||||
extraUuid = Arrays1.last(uuid.trim().split(Const.COLON));
|
||||
if (!Strings.isBlank(extraUuid)) {
|
||||
uuid = extraUuid.trim();
|
||||
}
|
||||
return SystemUuidUtils.uuid = Signatures.md5(uuid);
|
||||
} catch (Exception e) {
|
||||
// IGNORED
|
||||
}
|
||||
}
|
||||
return SystemUuidUtils.uuid = Const.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输出结果
|
||||
*
|
||||
* @param command command
|
||||
* @return result
|
||||
*/
|
||||
public static String getCommandOutput(String[] command) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ProcessAwaitExecutor executor = new ProcessAwaitExecutor(command);
|
||||
try {
|
||||
executor.streamHandler(i -> Attempt.uncheck(Streams::transfer, i, out))
|
||||
.waitFor()
|
||||
.sync()
|
||||
.exec();
|
||||
return out.toString();
|
||||
} finally {
|
||||
Streams.close(out);
|
||||
Streams.close(executor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.controller;
|
||||
|
||||
import cn.orionsec.kit.lang.define.wrapper.DataGrid;
|
||||
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.dromara.visor.common.validator.group.Page;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.annotation.OperatorLog;
|
||||
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
|
||||
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
|
||||
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
|
||||
import org.dromara.visor.module.terminal.define.operator.TerminalFileLogOperatorType;
|
||||
import org.dromara.visor.module.terminal.entity.request.terminal.TerminalFileLogQueryRequest;
|
||||
import org.dromara.visor.module.terminal.entity.vo.TerminalFileLogVO;
|
||||
import org.dromara.visor.module.terminal.service.TerminalFileLogService;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 终端文件日志操作服务 api
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023-12-26 22:09
|
||||
*/
|
||||
@Tag(name = "terminal - 终端文件日志操作服务")
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RestWrapper
|
||||
@RestController
|
||||
@RequestMapping("/terminal/terminal-file-log")
|
||||
public class TerminalFileLogController {
|
||||
|
||||
@Resource
|
||||
private TerminalFileLogService terminalFileLogService;
|
||||
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@PostMapping("/query")
|
||||
@Operation(summary = "分页查询终端文件操作日志")
|
||||
@PreAuthorize("@ss.hasAnyPermission('infra:operator-log:query', 'terminal:terminal-file-log:management:query')")
|
||||
public DataGrid<TerminalFileLogVO> getTerminalFileLogPage(@Validated(Page.class) @RequestBody TerminalFileLogQueryRequest request) {
|
||||
return terminalFileLogService.getTerminalFileLogPage(request);
|
||||
}
|
||||
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@PostMapping("/count")
|
||||
@Operation(summary = "查询终端文件操作日志数量")
|
||||
@PreAuthorize("@ss.hasAnyPermission('infra:operator-log:query', 'terminal:terminal-file-log:management:query')")
|
||||
public Long getTerminalFileLogCount(@Validated @RequestBody TerminalFileLogQueryRequest request) {
|
||||
return terminalFileLogService.getTerminalFileLogCount(request);
|
||||
}
|
||||
|
||||
@OperatorLog(TerminalFileLogOperatorType.DELETE)
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除终端文件操作日志")
|
||||
@Parameter(name = "idList", description = "idList", required = true)
|
||||
@PreAuthorize("@ss.hasAnyPermission('infra:operator-log:delete', 'terminal:terminal-file-log:management:delete')")
|
||||
public Integer deleteTerminalFileLog(@RequestParam("idList") List<Long> idList) {
|
||||
return terminalFileLogService.deleteTerminalFileLog(idList);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,22 +22,15 @@
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.controller;
|
||||
|
||||
import cn.orionsec.kit.lang.define.wrapper.DataGrid;
|
||||
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.dromara.visor.common.validator.group.Page;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.annotation.OperatorLog;
|
||||
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
|
||||
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
|
||||
import org.dromara.visor.framework.web.core.annotation.IgnoreWrapper;
|
||||
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
|
||||
import org.dromara.visor.module.terminal.define.operator.TerminalOperatorType;
|
||||
import org.dromara.visor.module.terminal.entity.request.terminal.TerminalSftpLogQueryRequest;
|
||||
import org.dromara.visor.module.terminal.entity.vo.TerminalSftpLogVO;
|
||||
import org.dromara.visor.module.terminal.service.TerminalSftpService;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -46,7 +39,6 @@ import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBo
|
||||
import javax.annotation.Resource;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SFTP 操作服务 api
|
||||
@@ -66,31 +58,6 @@ public class TerminalSftpController {
|
||||
@Resource
|
||||
private TerminalSftpService terminalSftpService;
|
||||
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@PostMapping("/query-log")
|
||||
@Operation(summary = "分页查询 SFTP 操作日志")
|
||||
@PreAuthorize("@ss.hasAnyPermission('infra:operator-log:query', 'terminal:terminal-sftp-log:management:query')")
|
||||
public DataGrid<TerminalSftpLogVO> getTerminalSftpLogPage(@Validated(Page.class) @RequestBody TerminalSftpLogQueryRequest request) {
|
||||
return terminalSftpService.getTerminalSftpLogPage(request);
|
||||
}
|
||||
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@PostMapping("/log-count")
|
||||
@Operation(summary = "查询 SFTP 操作日志数量")
|
||||
@PreAuthorize("@ss.hasAnyPermission('infra:operator-log:query', 'terminal:terminal-sftp-log:management:query')")
|
||||
public Long getTerminalSftpLogCount(@Validated @RequestBody TerminalSftpLogQueryRequest request) {
|
||||
return terminalSftpService.getTerminalSftpLogCount(request);
|
||||
}
|
||||
|
||||
@OperatorLog(TerminalOperatorType.DELETE_SFTP_LOG)
|
||||
@DeleteMapping("/delete-log")
|
||||
@Operation(summary = "删除 SFTP 操作日志")
|
||||
@Parameter(name = "idList", description = "idList", required = true)
|
||||
@PreAuthorize("@ss.hasAnyPermission('infra:operator-log:delete', 'terminal:terminal-sftp-log:management:delete')")
|
||||
public Integer deleteTerminalSftpLog(@RequestParam("idList") List<Long> idList) {
|
||||
return terminalSftpService.deleteTerminalSftpLog(idList);
|
||||
}
|
||||
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@GetMapping("/get-content")
|
||||
@Operation(summary = "获取文件内容")
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.convert;
|
||||
|
||||
import org.dromara.visor.module.infra.entity.dto.operator.OperatorLogDTO;
|
||||
import org.dromara.visor.module.terminal.entity.vo.TerminalFileLogVO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* 终端文件操作日志 内部对象转换器
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023-12-26 22:09
|
||||
*/
|
||||
@Mapper
|
||||
public interface TerminalFileLogConvert {
|
||||
|
||||
TerminalFileLogConvert MAPPER = Mappers.getMapper(TerminalFileLogConvert.class);
|
||||
|
||||
@Mapping(target = "extra", ignore = true)
|
||||
TerminalFileLogVO to(OperatorLogDTO request);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.define.operator;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.collect.Lists;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.annotation.Module;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.enums.OperatorRiskLevel;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 终端文件日志 操作日志类型
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/3/2 14:37
|
||||
*/
|
||||
@Module("terminal:terminal-file-log")
|
||||
public class TerminalFileLogOperatorType extends InitializingOperatorTypes {
|
||||
|
||||
public static final String DELETE = "terminal-file-log:delete";
|
||||
|
||||
public static final List<String> TYPES = Lists.of(
|
||||
TerminalOperatorType.SFTP_MKDIR,
|
||||
TerminalOperatorType.SFTP_TOUCH,
|
||||
TerminalOperatorType.SFTP_MOVE,
|
||||
TerminalOperatorType.SFTP_REMOVE,
|
||||
TerminalOperatorType.SFTP_TRUNCATE,
|
||||
TerminalOperatorType.SFTP_CHMOD,
|
||||
TerminalOperatorType.SFTP_CHOWN,
|
||||
TerminalOperatorType.SFTP_CHGRP,
|
||||
TerminalOperatorType.SFTP_GET_CONTENT,
|
||||
TerminalOperatorType.SFTP_SET_CONTENT,
|
||||
TerminalOperatorType.SFTP_UPLOAD,
|
||||
TerminalOperatorType.SFTP_DOWNLOAD,
|
||||
TerminalOperatorType.RDP_UPLOAD,
|
||||
TerminalOperatorType.RDP_DOWNLOAD
|
||||
);
|
||||
|
||||
@Override
|
||||
public OperatorType[] types() {
|
||||
return new OperatorType[]{
|
||||
new OperatorType(OperatorRiskLevel.H, DELETE, "删除文件操作日志 <sb>${count}</sb> 条"),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,14 +22,11 @@
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.define.operator;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.collect.Lists;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.annotation.Module;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.enums.OperatorRiskLevel;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 终端 操作日志类型
|
||||
*
|
||||
@@ -42,8 +39,6 @@ public class TerminalOperatorType extends InitializingOperatorTypes {
|
||||
|
||||
public static final String CONNECT = "terminal:connect";
|
||||
|
||||
public static final String DELETE_SFTP_LOG = "terminal:delete-sftp-log";
|
||||
|
||||
public static final String SFTP_MKDIR = "terminal:sftp-mkdir";
|
||||
|
||||
public static final String SFTP_TOUCH = "terminal:sftp-touch";
|
||||
@@ -68,38 +63,28 @@ public class TerminalOperatorType extends InitializingOperatorTypes {
|
||||
|
||||
public static final String SFTP_DOWNLOAD = "terminal:sftp-download";
|
||||
|
||||
public static final List<String> SFTP_TYPES = Lists.of(
|
||||
SFTP_MKDIR,
|
||||
SFTP_TOUCH,
|
||||
SFTP_MOVE,
|
||||
SFTP_REMOVE,
|
||||
SFTP_TRUNCATE,
|
||||
SFTP_CHMOD,
|
||||
SFTP_CHOWN,
|
||||
SFTP_CHGRP,
|
||||
SFTP_GET_CONTENT,
|
||||
SFTP_SET_CONTENT,
|
||||
SFTP_UPLOAD,
|
||||
SFTP_DOWNLOAD
|
||||
);
|
||||
public static final String RDP_UPLOAD = "terminal:rdp-upload";
|
||||
|
||||
public static final String RDP_DOWNLOAD = "terminal:rdp-download";
|
||||
|
||||
@Override
|
||||
public OperatorType[] types() {
|
||||
return new OperatorType[]{
|
||||
new OperatorType(OperatorRiskLevel.L, CONNECT, "连接主机 ${connectType} <sb>${hostName}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.H, DELETE_SFTP_LOG, "删除 SFTP 操作日志 <sb>${count}</sb> 条"),
|
||||
new OperatorType(OperatorRiskLevel.L, SFTP_MKDIR, "创建文件夹 ${hostName} <sb>${path}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.L, SFTP_TOUCH, "创建文件 ${hostName} <sb>${path}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.M, SFTP_MOVE, "移动文件 ${hostName} <sb>${path}</sb> 至 <sb>${target}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.H, SFTP_REMOVE, "删除文件 ${hostName} <sb>${path}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.H, SFTP_REMOVE, "删除文件 ${hostName} ${count}个\n<sb>${path}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.H, SFTP_TRUNCATE, "截断文件 ${hostName} <sb>${path}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.M, SFTP_CHMOD, "文件提权 ${hostName} <sb>${path}</sb> <sb>${mod}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.M, SFTP_CHOWN, "修改文件归属 ${hostName} <sb>${path}</sb> <sb>${id}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.M, SFTP_CHGRP, "修改文件分组 ${hostName} <sb>${path}</sb> <sb>${id}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.L, SFTP_GET_CONTENT, "获取文件内容 ${hostName} <sb>${path}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.M, SFTP_SET_CONTENT, "修改文件内容 ${hostName} <sb>${path}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.M, SFTP_UPLOAD, "上传文件 ${hostName} <sb>${path}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.M, SFTP_DOWNLOAD, "下载文件 ${hostName} <sb>${path}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.M, SFTP_UPLOAD, "上传文件 ${hostName} (${count}个)\n<sb>${path}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.M, SFTP_DOWNLOAD, "下载文件 ${hostName} (${count}个)\n<sb>${path}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.M, RDP_UPLOAD, "上传文件 ${hostName} <sb>${path}</sb>"),
|
||||
new OperatorType(OperatorRiskLevel.M, RDP_DOWNLOAD, "下载文件 ${hostName} <sb>${path}</sb>"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.entity.request.terminal;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import org.dromara.visor.common.entity.BaseQueryRequest;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 终端文件操作日志 查询请求对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024-3-4 22:59
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(name = "TerminalFileLogQueryRequest", description = "终端文件操作日志 查询请求对象")
|
||||
public class TerminalFileLogQueryRequest extends BaseQueryRequest {
|
||||
|
||||
@Schema(description = "用户id")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "hostId")
|
||||
private Long hostId;
|
||||
|
||||
@Size(max = 64)
|
||||
@Schema(description = "操作类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "操作结果 0失败 1成功")
|
||||
private Integer result;
|
||||
|
||||
@Schema(description = "开始时间-区间")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
private Date[] startTimeRange;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.entity.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 终端文件操作日志 实体对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023-10-10 17:08
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(name = "TerminalFileLogVO", description = "终端文件操作日志 实体对象")
|
||||
public class TerminalFileLogVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "id")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户id")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
@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;
|
||||
|
||||
@Schema(description = "请求地址")
|
||||
private String location;
|
||||
|
||||
@Schema(description = "userAgent")
|
||||
private String userAgent;
|
||||
|
||||
@Schema(description = "操作类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "参数")
|
||||
private Map<String, Object> extra;
|
||||
|
||||
@Schema(description = "操作结果 0失败 1成功")
|
||||
private Integer result;
|
||||
|
||||
@Schema(description = "开始时间")
|
||||
private Date startTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.enums;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.time.Dates;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
/**
|
||||
* 驱动挂载模式
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2025/6/29 1:32
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public enum DriveMountModeEnum {
|
||||
|
||||
/**
|
||||
* 完全共享
|
||||
*/
|
||||
SHARED("S") {
|
||||
@Override
|
||||
public String getDriveMountPath(Long userId, Long assetId, String sessionId) {
|
||||
return this.buildDriveMountPath(DEFAULT_S, userId, DEFAULT_N, DEFAULT_S);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 会话维度
|
||||
*/
|
||||
SESSION("SE") {
|
||||
@Override
|
||||
public String getDriveMountPath(Long userId, Long assetId, String sessionId) {
|
||||
return this.buildDriveMountPath(Dates.current(Dates.YMD2), userId, assetId, sessionId);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 资产维度
|
||||
*/
|
||||
ASSET("A") {
|
||||
@Override
|
||||
public String getDriveMountPath(Long userId, Long assetId, String sessionId) {
|
||||
return this.buildDriveMountPath(DEFAULT_S, userId, assetId, DEFAULT_S);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 天维度
|
||||
*/
|
||||
DAY("D") {
|
||||
@Override
|
||||
public String getDriveMountPath(Long userId, Long assetId, String sessionId) {
|
||||
return this.buildDriveMountPath(Dates.current(Dates.YMD2), userId, DEFAULT_N, DEFAULT_S);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 天维度 + 资产维度
|
||||
*/
|
||||
DAY_ASSET("DA") {
|
||||
@Override
|
||||
public String getDriveMountPath(Long userId, Long assetId, String sessionId) {
|
||||
return this.buildDriveMountPath(Dates.current(Dates.YMD2), userId, DEFAULT_N, DEFAULT_S);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 月维度
|
||||
*/
|
||||
MONTH("M") {
|
||||
@Override
|
||||
public String getDriveMountPath(Long userId, Long assetId, String sessionId) {
|
||||
String date = Dates.stream()
|
||||
.setDay(1)
|
||||
.format(Dates.YMD2);
|
||||
return this.buildDriveMountPath(date, userId, DEFAULT_N, DEFAULT_S);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 月维度 + 资产维度
|
||||
*/
|
||||
MONTH_ASSET("MA") {
|
||||
@Override
|
||||
public String getDriveMountPath(Long userId, Long assetId, String sessionId) {
|
||||
String date = Dates.stream()
|
||||
.setDay(1)
|
||||
.format(Dates.YMD2);
|
||||
return this.buildDriveMountPath(date, userId, assetId, DEFAULT_S);
|
||||
}
|
||||
},
|
||||
|
||||
;
|
||||
|
||||
private static final Long DEFAULT_N = 0L;
|
||||
|
||||
private static final String DEFAULT_S = "0";
|
||||
|
||||
private final String prefix;
|
||||
|
||||
/**
|
||||
* 获取驱动挂载路径
|
||||
*
|
||||
* @param userId userId
|
||||
* @param assetId assetId
|
||||
* @param sessionId sessionId
|
||||
* @return path
|
||||
*/
|
||||
public abstract String getDriveMountPath(Long userId, Long assetId, String sessionId);
|
||||
|
||||
/**
|
||||
* 构建驱动挂载路径
|
||||
*
|
||||
* @param time time
|
||||
* @param userId userId
|
||||
* @param assetId assetId
|
||||
* @param sessionId sessionId
|
||||
* @return path
|
||||
*/
|
||||
protected String buildDriveMountPath(String time, Long userId, Long assetId, String sessionId) {
|
||||
return prefix + "_"
|
||||
+ time + "_"
|
||||
+ userId + "_"
|
||||
+ assetId + "_"
|
||||
+ sessionId;
|
||||
}
|
||||
|
||||
public static DriveMountModeEnum of(String mode) {
|
||||
if (mode == null) {
|
||||
return ASSET;
|
||||
}
|
||||
for (DriveMountModeEnum value : values()) {
|
||||
if (value.name().equalsIgnoreCase(mode)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return ASSET;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -306,10 +306,15 @@ public interface GuacdConst {
|
||||
String DISABLE_OFFSCREEN_CACHING = "disable-offscreen-caching";
|
||||
|
||||
/**
|
||||
* 禁用字形缓存 boolean 默认禁用
|
||||
* 禁用字形缓存 boolean
|
||||
*/
|
||||
String DISABLE_GLYPH_CACHING = "disable-glyph-caching";
|
||||
|
||||
/**
|
||||
* 禁用图形加速 boolean
|
||||
*/
|
||||
String DISABLE_GFX = "disable-gfx";
|
||||
|
||||
/**
|
||||
* 远程应用名称
|
||||
*/
|
||||
|
||||
@@ -43,7 +43,7 @@ import javax.annotation.PostConstruct;
|
||||
public enum InputProtocolEnum {
|
||||
|
||||
/**
|
||||
* 连接终端
|
||||
* 请求连接
|
||||
*/
|
||||
CONNECT("co",
|
||||
TerminalConnectHandler.class,
|
||||
@@ -74,7 +74,17 @@ public enum InputProtocolEnum {
|
||||
new String[]{"type", "width", "height"},
|
||||
TerminalResizeRequest.class),
|
||||
|
||||
// ----------------------- SSH ----------------------
|
||||
// ----------------------- guacd ----------------------
|
||||
|
||||
/**
|
||||
* guacd 指令
|
||||
*/
|
||||
GUACD_INSTRUCTION("gi",
|
||||
GuacdInstructionHandler.class,
|
||||
new String[]{"type", "instruction"},
|
||||
GuacdInstructionRequest.class),
|
||||
|
||||
// ----------------------- ssh ----------------------
|
||||
|
||||
/**
|
||||
* SSH 输入
|
||||
@@ -84,7 +94,7 @@ public enum InputProtocolEnum {
|
||||
new String[]{"type", "command"},
|
||||
SshInputRequest.class),
|
||||
|
||||
// ----------------------- SFTP ----------------------
|
||||
// ----------------------- sftp ----------------------
|
||||
|
||||
/**
|
||||
* SFTP 文件列表
|
||||
@@ -182,15 +192,15 @@ public enum InputProtocolEnum {
|
||||
new String[]{"type", "path"},
|
||||
SftpBaseRequest.class),
|
||||
|
||||
// ----------------------- guacd ----------------------
|
||||
// ----------------------- rdp ----------------------
|
||||
|
||||
/**
|
||||
* guacd 指令
|
||||
* RDP 文件系统事件
|
||||
*/
|
||||
GUACD_INSTRUCTION("gi",
|
||||
GuacdInstructionHandler.class,
|
||||
new String[]{"type", "instruction"},
|
||||
GuacdInstructionRequest.class),
|
||||
RDP_FILE_SYSTEM_EVENT("fse",
|
||||
RdpFileSystemEventHandler.class,
|
||||
new String[]{"type", "event"},
|
||||
RdpFileSystemEventRequest.class),
|
||||
|
||||
;
|
||||
|
||||
|
||||
@@ -69,6 +69,13 @@ public enum OutputProtocolEnum {
|
||||
*/
|
||||
RESIZE("rs", "${type}|${width}|${height}"),
|
||||
|
||||
// ----------------------- guacd ----------------------
|
||||
|
||||
/**
|
||||
* guacd 指令
|
||||
*/
|
||||
GUACD_INSTRUCTION("gi", "${type}|${instruction}"),
|
||||
|
||||
// ----------------------- ssh ----------------------
|
||||
|
||||
/**
|
||||
@@ -143,13 +150,6 @@ public enum OutputProtocolEnum {
|
||||
*/
|
||||
SFTP_SET_CONTENT("sc", "${type}|${result}|${msg}|${token}"),
|
||||
|
||||
// ----------------------- guacd ----------------------
|
||||
|
||||
/**
|
||||
* guacd 指令
|
||||
*/
|
||||
GUACD_INSTRUCTION("gi", "${type}|${instruction}"),
|
||||
|
||||
;
|
||||
|
||||
private final String type;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.handler.terminal.handler;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.module.terminal.define.operator.TerminalOperatorType;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelProps;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.request.RdpFileSystemEventRequest;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.transport.RdpFileSystemEvent;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.sender.IGuacdTerminalSender;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* rdp 文件系统事件 处理器
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/2/19 11:13
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RdpFileSystemEventHandler extends AbstractTerminalHandler<IGuacdTerminalSender, RdpFileSystemEventRequest> {
|
||||
|
||||
@Override
|
||||
public void handle(TerminalChannelProps props, IGuacdTerminalSender sender, RdpFileSystemEventRequest payload) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
String sessionId = props.getId();
|
||||
// 获取会话
|
||||
RdpFileSystemEvent fsEvent = JSON.parseObject(payload.getEvent(), RdpFileSystemEvent.class);
|
||||
String event = fsEvent.getEvent();
|
||||
String path = fsEvent.getPath();
|
||||
log.info("RdpFileSystemEventHandler-handle start sessionId: {}, event: {}, path: {}", sessionId, event, path);
|
||||
String operatorType;
|
||||
if (TerminalOperatorType.RDP_UPLOAD.equals(event)) {
|
||||
// 上传文件
|
||||
operatorType = TerminalOperatorType.RDP_UPLOAD;
|
||||
} else if (TerminalOperatorType.RDP_DOWNLOAD.equals(event)) {
|
||||
// 下载文件
|
||||
operatorType = TerminalOperatorType.RDP_DOWNLOAD;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
// 保存操作日志
|
||||
Map<String, Object> extra = Maps.newMap();
|
||||
extra.put(OperatorLogs.PATH, path);
|
||||
this.saveOperatorLog(props, extra, operatorType, startTime, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import org.dromara.visor.module.terminal.handler.terminal.model.request.SftpDown
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.response.SftpFileVO;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.sender.ISftpTerminalSender;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.session.ISftpSession;
|
||||
import org.dromara.visor.module.terminal.utils.SftpFileUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -50,7 +51,7 @@ public class SftpDownloadFlatDirectoryHandler extends AbstractTerminalHandler<IS
|
||||
// 获取会话
|
||||
String sessionId = props.getId();
|
||||
ISftpSession session = terminalManager.getSession(props.getId());
|
||||
String[] paths = payload.getPath().split("\\|");
|
||||
String[] paths = SftpFileUtils.fromMultiPaths(payload.getPath());
|
||||
log.info("SftpDownloadFlatDirectoryHandler-handle start sessionId: {}, paths: {}", sessionId, Arrays.toString(paths));
|
||||
Exception ex = null;
|
||||
List<SftpFileVO> files = Lists.empty();
|
||||
|
||||
@@ -24,12 +24,14 @@ package org.dromara.visor.module.terminal.handler.terminal.handler;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.module.terminal.define.operator.TerminalOperatorType;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelProps;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.request.SftpBaseRequest;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.sender.ISftpTerminalSender;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.session.ISftpSession;
|
||||
import org.dromara.visor.module.terminal.utils.SftpFileUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -49,11 +51,11 @@ public class SftpRemoveHandler extends AbstractTerminalHandler<ISftpTerminalSend
|
||||
@Override
|
||||
public void handle(TerminalChannelProps props, ISftpTerminalSender sender, SftpBaseRequest payload) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
String path = payload.getPath();
|
||||
String[] paths = SftpFileUtils.fromMultiPaths(payload.getPath());
|
||||
String path = String.join(Const.LF, paths);
|
||||
String sessionId = props.getId();
|
||||
// 获取会话
|
||||
ISftpSession session = terminalManager.getSession(sessionId);
|
||||
String[] paths = path.split("\\|");
|
||||
log.info("SftpRemoveHandler-handle start sessionId: {}, path: {}", sessionId, Arrays.toString(paths));
|
||||
Exception ex = null;
|
||||
// 删除
|
||||
@@ -69,6 +71,7 @@ public class SftpRemoveHandler extends AbstractTerminalHandler<ISftpTerminalSend
|
||||
// 保存操作日志
|
||||
Map<String, Object> extra = Maps.newMap();
|
||||
extra.put(OperatorLogs.PATH, path);
|
||||
extra.put(OperatorLogs.COUNT, paths.length);
|
||||
this.saveOperatorLog(props,
|
||||
extra, TerminalOperatorType.SFTP_REMOVE,
|
||||
startTime, ex);
|
||||
|
||||
@@ -129,6 +129,7 @@ public class TerminalConnectHandler extends AbstractTerminalHandler<ITerminalSen
|
||||
this.updateTerminalConnectLog(logId, null, null);
|
||||
// 发送设置信息
|
||||
sender.sendSetInfo(TerminalSetInfo.builder()
|
||||
.logId(logId)
|
||||
.address(connectConfig.getHostAddress())
|
||||
.port(connectConfig.getHostPort())
|
||||
.username(connectConfig.getUsername())
|
||||
|
||||
@@ -126,6 +126,16 @@ public class TerminalChannelExtra {
|
||||
*/
|
||||
private Boolean disableGlyphCaching;
|
||||
|
||||
/**
|
||||
* 禁用图形加速
|
||||
*/
|
||||
private Boolean disableGfx;
|
||||
|
||||
/**
|
||||
* 驱动挂载模式
|
||||
*/
|
||||
private String driveMountMode;
|
||||
|
||||
// -------------------- vnc --------------------
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.handler.terminal.model.request;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalBasePayload;
|
||||
|
||||
/**
|
||||
* rdp 文件系统事件请求
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/2/6 13:31
|
||||
*/
|
||||
@Data
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class RdpFileSystemEventRequest extends TerminalBasePayload {
|
||||
|
||||
/**
|
||||
* 事件
|
||||
*/
|
||||
private String event;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.handler.terminal.model.transport;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* RDP 文件系统事件
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2025/6/30 22:33
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RdpFileSystemEvent {
|
||||
|
||||
/**
|
||||
* 事件
|
||||
*/
|
||||
private String event;
|
||||
|
||||
/**
|
||||
* 文件路径
|
||||
*/
|
||||
private String path;
|
||||
|
||||
}
|
||||
@@ -41,6 +41,11 @@ import lombok.experimental.SuperBuilder;
|
||||
@AllArgsConstructor
|
||||
public class TerminalSetInfo implements IJsonObject {
|
||||
|
||||
/**
|
||||
* logId
|
||||
*/
|
||||
private Long logId;
|
||||
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
|
||||
@@ -25,11 +25,11 @@ package org.dromara.visor.module.terminal.handler.terminal.session;
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.io.Files1;
|
||||
import cn.orionsec.kit.lang.utils.time.Dates;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.AppConst;
|
||||
import org.dromara.visor.common.utils.AesEncryptUtils;
|
||||
import org.dromara.visor.module.common.config.GuacdConfig;
|
||||
import org.dromara.visor.module.terminal.enums.DriveMountModeEnum;
|
||||
import org.dromara.visor.module.terminal.handler.guacd.GuacdTunnel;
|
||||
import org.dromara.visor.module.terminal.handler.guacd.IGuacdTunnel;
|
||||
import org.dromara.visor.module.terminal.handler.guacd.constant.GuacdConst;
|
||||
@@ -101,9 +101,10 @@ public class RdpSession extends AbstractGuacdSession<TerminalSessionRdpConfig> i
|
||||
tunnel.setParameter(GuacdConst.ENABLE_FULL_WINDOW_DRAG, extra.getEnableFullWindowDrag());
|
||||
tunnel.setParameter(GuacdConst.ENABLE_DESKTOP_COMPOSITION, extra.getEnableDesktopComposition());
|
||||
tunnel.setParameter(GuacdConst.ENABLE_MENU_ANIMATIONS, extra.getEnableMenuAnimations());
|
||||
tunnel.setParameter(GuacdConst.DISABLE_BITMAP_CACHING, extra.getDisableBitmapCaching());
|
||||
tunnel.setParameter(GuacdConst.DISABLE_OFFSCREEN_CACHING, extra.getDisableOffscreenCaching());
|
||||
tunnel.setParameter(GuacdConst.DISABLE_GLYPH_CACHING, extra.getDisableGlyphCaching());
|
||||
tunnel.setParameter(GuacdConst.DISABLE_BITMAP_CACHING, extra.getDisableBitmapCaching());
|
||||
tunnel.setParameter(GuacdConst.DISABLE_GFX, extra.getDisableGfx());
|
||||
// 音频
|
||||
tunnel.setAudioMimeTypes(GuacdConst.AUDIO_MIMETYPES);
|
||||
tunnel.setParameter(GuacdConst.ENABLE_AUDIO_INPUT, extra.getEnableAudioInput());
|
||||
@@ -113,8 +114,15 @@ public class RdpSession extends AbstractGuacdSession<TerminalSessionRdpConfig> i
|
||||
tunnel.setParameter(GuacdConst.ENABLE_DRIVE, true);
|
||||
tunnel.setParameter(GuacdConst.CREATE_DRIVE_PATH, true);
|
||||
tunnel.setParameter(GuacdConst.DRIVE_NAME, GuacdConst.DRIVE_DRIVE_NAME);
|
||||
// 父文件夹必须存在 所以只能用 _ 分
|
||||
tunnel.setParameter(GuacdConst.DRIVE_PATH, Files1.getPath(guacdConfig.getDrivePath() + "/" + Dates.current(Dates.YMD2) + "_" + props.getUserId() + "_" + props.getHostId()));
|
||||
// 父文件夹必须存在 否则会报错 所以不能分层
|
||||
String driveMountPath = DriveMountModeEnum.of(extra.getDriveMountMode())
|
||||
.getDriveMountPath(props.getUserId(), props.getHostId(), props.getId());
|
||||
tunnel.setParameter(GuacdConst.DRIVE_PATH, Files1.getPath(guacdConfig.getDrivePath() + "/" + driveMountPath));
|
||||
// 初始化程序
|
||||
String initialProgram = config.getInitialProgram();
|
||||
if (!Strings.isBlank(initialProgram)) {
|
||||
tunnel.setParameter(GuacdConst.INITIAL_PROGRAM, initialProgram);
|
||||
}
|
||||
// 预连接
|
||||
String preConnectionId = config.getPreConnectionId();
|
||||
if (!Strings.isBlank(preConnectionId)) {
|
||||
@@ -153,8 +161,9 @@ public class RdpSession extends AbstractGuacdSession<TerminalSessionRdpConfig> i
|
||||
extra.setEnableDesktopComposition(false);
|
||||
extra.setEnableMenuAnimations(false);
|
||||
extra.setDisableBitmapCaching(false);
|
||||
extra.setDisableOffscreenCaching(false);
|
||||
extra.setDisableGlyphCaching(false);
|
||||
extra.setDisableBitmapCaching(false);
|
||||
extra.setDisableGfx(false);
|
||||
extra.setEnableAudioInput(false);
|
||||
extra.setEnableAudioOutput(false);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.module.common.utils.SftpUtils;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelProps;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.config.TerminalSessionSftpConfig;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.response.SftpFileVO;
|
||||
@@ -117,7 +116,7 @@ public class SftpSession extends AbstractTerminalSession<ISftpTerminalSender, Te
|
||||
public void move(String source, String target) {
|
||||
// 计算路径
|
||||
source = Valid.checkNormalize(source);
|
||||
target = SftpUtils.getAbsoluteTargetPath(source, target);
|
||||
target = SftpFileUtils.getAbsoluteTargetPath(source, target);
|
||||
// 移动
|
||||
executor.move(source, target);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件操作请求 实体对象
|
||||
*
|
||||
@@ -42,6 +40,11 @@ import java.util.List;
|
||||
@AllArgsConstructor
|
||||
public class TransferOperatorRequest {
|
||||
|
||||
/**
|
||||
* 日志id
|
||||
*/
|
||||
private Long logId;
|
||||
|
||||
/**
|
||||
* 文件路径
|
||||
*/
|
||||
@@ -62,11 +65,6 @@ public class TransferOperatorRequest {
|
||||
*/
|
||||
private Long hostId;
|
||||
|
||||
/**
|
||||
* 被压缩文件路径
|
||||
*/
|
||||
private List<String> paths;
|
||||
|
||||
/**
|
||||
* 错误信息 后端赋值
|
||||
*/
|
||||
|
||||
@@ -25,6 +25,7 @@ package org.dromara.visor.module.terminal.handler.transfer.session;
|
||||
import cn.orionsec.kit.lang.define.wrapper.Ref;
|
||||
import cn.orionsec.kit.lang.utils.Threads;
|
||||
import cn.orionsec.kit.lang.utils.Valid;
|
||||
import cn.orionsec.kit.lang.utils.collect.Lists;
|
||||
import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import cn.orionsec.kit.net.host.SessionStore;
|
||||
import cn.orionsec.kit.net.host.sftp.SftpFile;
|
||||
@@ -72,7 +73,7 @@ public class DownloadSession extends TransferSession implements StreamingRespons
|
||||
super.onStart(request);
|
||||
log.info("DownloadSession.startDownload open start channelId: {}, path: {}", channelId, path);
|
||||
// 保存操作日志
|
||||
this.saveOperatorLog(TerminalOperatorType.SFTP_DOWNLOAD, path);
|
||||
this.saveOperatorLog(request.getLogId(), TerminalOperatorType.SFTP_DOWNLOAD, Lists.singleton(path));
|
||||
// 检查连接
|
||||
this.init();
|
||||
// 检查文件是否存在
|
||||
|
||||
@@ -30,6 +30,7 @@ import cn.orionsec.kit.net.host.sftp.SftpExecutor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.constant.FieldConst;
|
||||
import org.dromara.visor.common.session.config.SshConnectConfig;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorLogModel;
|
||||
@@ -43,6 +44,7 @@ import org.dromara.visor.module.terminal.handler.transfer.model.TransferOperator
|
||||
import org.dromara.visor.module.terminal.handler.transfer.utils.TransferUtils;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -147,20 +149,25 @@ public abstract class TransferSession implements ITransferSession {
|
||||
/**
|
||||
* 保存操作日志
|
||||
*
|
||||
* @param type type
|
||||
* @param path path
|
||||
* @param logId logId
|
||||
* @param type type
|
||||
* @param paths paths
|
||||
*/
|
||||
protected void saveOperatorLog(String type, String path) {
|
||||
// 设置参数
|
||||
protected void saveOperatorLog(Long logId, String type, List<String> paths) {
|
||||
TerminalChannelProps props = WebSockets.getAttr(channel, FieldConst.PROPS);
|
||||
String path = String.join(Const.LF, paths);
|
||||
int count = paths.size();
|
||||
// 获取操作日志
|
||||
Map<String, Object> extra = Maps.newMap();
|
||||
extra.put(OperatorLogs.PATH, path);
|
||||
extra.put(OperatorLogs.COUNT, count);
|
||||
extra.put(OperatorLogs.HOST_ID, connectConfig.getHostId());
|
||||
extra.put(OperatorLogs.HOST_NAME, connectConfig.getHostName());
|
||||
extra.put(OperatorLogs.ADDRESS, connectConfig.getHostAddress());
|
||||
// 获取日志
|
||||
TerminalChannelProps props = WebSockets.getAttr(channel, FieldConst.PROPS);
|
||||
OperatorLogModel model = TerminalUtils.getOperatorLogModel(props, extra, type, System.currentTimeMillis(), null);
|
||||
// 保存
|
||||
// 保存操作日志
|
||||
TerminalAsyncSaver.saveOperatorLog(model);
|
||||
// 保存操作日志
|
||||
TerminalAsyncSaver.saveOperatorLog(model);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.handler.transfer.session;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.collect.Lists;
|
||||
import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import cn.orionsec.kit.net.host.SessionStore;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -58,7 +59,7 @@ public class UploadSession extends TransferSession {
|
||||
try {
|
||||
log.info("UploadSession.startUpload start channelId: {}, path: {}", channelId, path);
|
||||
// 保存操作日志
|
||||
this.saveOperatorLog(TerminalOperatorType.SFTP_UPLOAD, path);
|
||||
this.saveOperatorLog(request.getLogId(), TerminalOperatorType.SFTP_UPLOAD, Lists.singleton(path));
|
||||
// 检查连接
|
||||
this.init();
|
||||
// 检查文件是否存在
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.service;
|
||||
|
||||
import cn.orionsec.kit.lang.define.wrapper.DataGrid;
|
||||
import org.dromara.visor.module.terminal.entity.request.terminal.TerminalFileLogQueryRequest;
|
||||
import org.dromara.visor.module.terminal.entity.vo.TerminalFileLogVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 终端文件日志服务类
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023-12-26 22:09
|
||||
*/
|
||||
public interface TerminalFileLogService {
|
||||
|
||||
/**
|
||||
* 分页查询终端文件操作日志
|
||||
*
|
||||
* @param request request
|
||||
* @return rows
|
||||
*/
|
||||
DataGrid<TerminalFileLogVO> getTerminalFileLogPage(TerminalFileLogQueryRequest request);
|
||||
|
||||
/**
|
||||
* 获取终端文件操作日志数量
|
||||
*
|
||||
* @param request request
|
||||
* @return count
|
||||
*/
|
||||
Long getTerminalFileLogCount(TerminalFileLogQueryRequest request);
|
||||
|
||||
/**
|
||||
* 删除终端文件操作日志
|
||||
*
|
||||
* @param idList idList
|
||||
* @return effect
|
||||
*/
|
||||
Integer deleteTerminalFileLog(List<Long> idList);
|
||||
|
||||
}
|
||||
@@ -22,15 +22,11 @@
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.service;
|
||||
|
||||
import cn.orionsec.kit.lang.define.wrapper.DataGrid;
|
||||
import org.dromara.visor.module.terminal.entity.request.terminal.TerminalSftpLogQueryRequest;
|
||||
import org.dromara.visor.module.terminal.entity.vo.TerminalSftpLogVO;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SFTP 操作 服务类
|
||||
@@ -41,30 +37,6 @@ import java.util.List;
|
||||
*/
|
||||
public interface TerminalSftpService {
|
||||
|
||||
/**
|
||||
* 分页查询 SFTP 操作日志
|
||||
*
|
||||
* @param request request
|
||||
* @return rows
|
||||
*/
|
||||
DataGrid<TerminalSftpLogVO> getTerminalSftpLogPage(TerminalSftpLogQueryRequest request);
|
||||
|
||||
/**
|
||||
* 获取 SFTP 操作日志数量
|
||||
*
|
||||
* @param request request
|
||||
* @return count
|
||||
*/
|
||||
Long getTerminalSftpLogCount(TerminalSftpLogQueryRequest request);
|
||||
|
||||
/**
|
||||
* 删除 SFTP 操作日志
|
||||
*
|
||||
* @param idList idList
|
||||
* @return effect
|
||||
*/
|
||||
Integer deleteTerminalSftpLog(List<Long> idList);
|
||||
|
||||
/**
|
||||
* 设置文件内容
|
||||
*
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.service.impl;
|
||||
|
||||
import cn.orionsec.kit.lang.define.wrapper.DataGrid;
|
||||
import cn.orionsec.kit.lang.utils.Arrays1;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.ExtraFieldConst;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.module.infra.api.OperatorLogApi;
|
||||
import org.dromara.visor.module.infra.entity.dto.operator.OperatorLogQueryDTO;
|
||||
import org.dromara.visor.module.terminal.convert.TerminalFileLogConvert;
|
||||
import org.dromara.visor.module.terminal.define.operator.TerminalFileLogOperatorType;
|
||||
import org.dromara.visor.module.terminal.entity.request.terminal.TerminalFileLogQueryRequest;
|
||||
import org.dromara.visor.module.terminal.entity.vo.TerminalFileLogVO;
|
||||
import org.dromara.visor.module.terminal.service.TerminalFileLogService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 终端文件日志服务实现类
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/3/4 23:35
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TerminalFileLogServiceImpl implements TerminalFileLogService {
|
||||
|
||||
@Resource
|
||||
private OperatorLogApi operatorLogApi;
|
||||
|
||||
@Override
|
||||
public DataGrid<TerminalFileLogVO> getTerminalFileLogPage(TerminalFileLogQueryRequest request) {
|
||||
// 查询
|
||||
OperatorLogQueryDTO query = this.buildQueryInfo(request);
|
||||
// 转换
|
||||
return operatorLogApi.getOperatorLogPage(query)
|
||||
.map(s -> {
|
||||
JSONObject extra = JSON.parseObject(s.getExtra());
|
||||
TerminalFileLogVO vo = TerminalFileLogConvert.MAPPER.to(s);
|
||||
vo.setHostId(extra.getLong(ExtraFieldConst.HOST_ID));
|
||||
vo.setHostName(extra.getString(ExtraFieldConst.HOST_NAME));
|
||||
vo.setHostAddress(extra.getString(ExtraFieldConst.ADDRESS));
|
||||
String[] paths = Optional.ofNullable(extra.getString(ExtraFieldConst.PATH))
|
||||
.map(p -> p.split("\\|"))
|
||||
.orElse(new String[0]);
|
||||
vo.setPaths(paths);
|
||||
vo.setExtra(extra);
|
||||
return vo;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTerminalFileLogCount(TerminalFileLogQueryRequest request) {
|
||||
// 查询
|
||||
OperatorLogQueryDTO query = this.buildQueryInfo(request);
|
||||
// 转换
|
||||
return operatorLogApi.getOperatorLogCount(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer deleteTerminalFileLog(List<Long> idList) {
|
||||
log.info("TerminalSftpService.deleteTerminalFileLog start {}", JSON.toJSONString(idList));
|
||||
Integer effect = operatorLogApi.deleteOperatorLog(idList);
|
||||
log.info("TerminalSftpService.deleteTerminalFileLog finish {}", effect);
|
||||
// 设置日志参数
|
||||
OperatorLogs.add(OperatorLogs.COUNT, effect);
|
||||
return effect;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询对象
|
||||
*
|
||||
* @param request request
|
||||
* @return query
|
||||
*/
|
||||
private OperatorLogQueryDTO buildQueryInfo(TerminalFileLogQueryRequest request) {
|
||||
Long hostId = request.getHostId();
|
||||
String type = request.getType();
|
||||
// 构建参数
|
||||
OperatorLogQueryDTO query = OperatorLogQueryDTO.builder()
|
||||
.userId(request.getUserId())
|
||||
.result(request.getResult())
|
||||
.startTimeStart(Arrays1.getIfPresent(request.getStartTimeRange(), 0))
|
||||
.startTimeEnd(Arrays1.getIfPresent(request.getStartTimeRange(), 1))
|
||||
.build();
|
||||
query.setPage(request.getPage());
|
||||
query.setLimit(request.getLimit());
|
||||
query.setOrder(request.getOrder());
|
||||
if (Strings.isBlank(type)) {
|
||||
// 查询全部文件操作类型
|
||||
query.setTypeList(TerminalFileLogOperatorType.TYPES);
|
||||
} else {
|
||||
query.setType(type);
|
||||
}
|
||||
// 模糊查询
|
||||
if (hostId != null) {
|
||||
query.setExtra("\"hostId\": " + hostId + ",");
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,9 +23,7 @@
|
||||
package org.dromara.visor.module.terminal.service.impl;
|
||||
|
||||
import cn.orionsec.kit.lang.constant.StandardContentType;
|
||||
import cn.orionsec.kit.lang.define.wrapper.DataGrid;
|
||||
import cn.orionsec.kit.lang.define.wrapper.HttpWrapper;
|
||||
import cn.orionsec.kit.lang.utils.Arrays1;
|
||||
import cn.orionsec.kit.lang.utils.Exceptions;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.Valid;
|
||||
@@ -34,27 +32,17 @@ import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import cn.orionsec.kit.net.host.SessionStore;
|
||||
import cn.orionsec.kit.net.host.sftp.SftpExecutor;
|
||||
import cn.orionsec.kit.web.servlet.web.Servlets;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.constant.ExtraFieldConst;
|
||||
import org.dromara.visor.common.session.config.SshConnectConfig;
|
||||
import org.dromara.visor.common.session.ssh.SessionStores;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.framework.redis.core.utils.RedisStrings;
|
||||
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
|
||||
import org.dromara.visor.module.asset.api.HostConnectApi;
|
||||
import org.dromara.visor.module.infra.api.OperatorLogApi;
|
||||
import org.dromara.visor.module.infra.entity.dto.operator.OperatorLogQueryDTO;
|
||||
import org.dromara.visor.module.terminal.convert.TerminalSftpLogConvert;
|
||||
import org.dromara.visor.module.terminal.define.cache.TerminalCacheKeyDefine;
|
||||
import org.dromara.visor.module.terminal.define.operator.TerminalOperatorType;
|
||||
import org.dromara.visor.module.terminal.entity.dto.SftpGetContentCacheDTO;
|
||||
import org.dromara.visor.module.terminal.entity.dto.SftpSetContentCacheDTO;
|
||||
import org.dromara.visor.module.terminal.entity.request.terminal.TerminalSftpLogQueryRequest;
|
||||
import org.dromara.visor.module.terminal.entity.vo.TerminalSftpLogVO;
|
||||
import org.dromara.visor.module.terminal.handler.transfer.manager.TerminalTransferManager;
|
||||
import org.dromara.visor.module.terminal.handler.transfer.session.DownloadSession;
|
||||
import org.dromara.visor.module.terminal.service.TerminalSftpService;
|
||||
@@ -67,7 +55,6 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -81,54 +68,12 @@ import java.util.Optional;
|
||||
@Service
|
||||
public class TerminalSftpServiceImpl implements TerminalSftpService {
|
||||
|
||||
@Resource
|
||||
private OperatorLogApi operatorLogApi;
|
||||
|
||||
@Resource
|
||||
private HostConnectApi hostConnectApi;
|
||||
|
||||
@Resource
|
||||
private TerminalTransferManager terminalTransferManager;
|
||||
|
||||
@Override
|
||||
public DataGrid<TerminalSftpLogVO> getTerminalSftpLogPage(TerminalSftpLogQueryRequest request) {
|
||||
// 查询
|
||||
OperatorLogQueryDTO query = this.buildQueryInfo(request);
|
||||
// 转换
|
||||
return operatorLogApi.getOperatorLogPage(query)
|
||||
.map(s -> {
|
||||
JSONObject extra = JSON.parseObject(s.getExtra());
|
||||
TerminalSftpLogVO vo = TerminalSftpLogConvert.MAPPER.to(s);
|
||||
vo.setHostId(extra.getLong(ExtraFieldConst.HOST_ID));
|
||||
vo.setHostName(extra.getString(ExtraFieldConst.HOST_NAME));
|
||||
vo.setHostAddress(extra.getString(ExtraFieldConst.ADDRESS));
|
||||
String[] paths = Optional.ofNullable(extra.getString(ExtraFieldConst.PATH))
|
||||
.map(p -> p.split("\\|"))
|
||||
.orElse(new String[0]);
|
||||
vo.setPaths(paths);
|
||||
vo.setExtra(extra);
|
||||
return vo;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTerminalSftpLogCount(TerminalSftpLogQueryRequest request) {
|
||||
// 查询
|
||||
OperatorLogQueryDTO query = this.buildQueryInfo(request);
|
||||
// 转换
|
||||
return operatorLogApi.getOperatorLogCount(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer deleteTerminalSftpLog(List<Long> idList) {
|
||||
log.info("TerminalSftpService.deleteSftpLog start {}", JSON.toJSONString(idList));
|
||||
Integer effect = operatorLogApi.deleteOperatorLog(idList);
|
||||
log.info("TerminalSftpService.deleteSftpLog finish {}", effect);
|
||||
// 设置日志参数
|
||||
OperatorLogs.add(OperatorLogs.COUNT, effect);
|
||||
return effect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getFileContentByToken(String token, HttpServletResponse response) throws IOException {
|
||||
// 解析 token
|
||||
@@ -215,36 +160,4 @@ public class TerminalSftpServiceImpl implements TerminalSftpService {
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询对象
|
||||
*
|
||||
* @param request request
|
||||
* @return query
|
||||
*/
|
||||
private OperatorLogQueryDTO buildQueryInfo(TerminalSftpLogQueryRequest request) {
|
||||
Long hostId = request.getHostId();
|
||||
String type = request.getType();
|
||||
// 构建参数
|
||||
OperatorLogQueryDTO query = OperatorLogQueryDTO.builder()
|
||||
.userId(request.getUserId())
|
||||
.result(request.getResult())
|
||||
.startTimeStart(Arrays1.getIfPresent(request.getStartTimeRange(), 0))
|
||||
.startTimeEnd(Arrays1.getIfPresent(request.getStartTimeRange(), 1))
|
||||
.build();
|
||||
query.setPage(request.getPage());
|
||||
query.setLimit(request.getLimit());
|
||||
query.setOrder(request.getOrder());
|
||||
if (Strings.isBlank(type)) {
|
||||
// 查询全部 SFTP 类型
|
||||
query.setTypeList(TerminalOperatorType.SFTP_TYPES);
|
||||
} else {
|
||||
query.setType(type);
|
||||
}
|
||||
// 模糊查询
|
||||
if (hostId != null) {
|
||||
query.setExtra("\"hostId\": " + hostId + ",");
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.utils;
|
||||
|
||||
import cn.orionsec.kit.lang.constant.Letters;
|
||||
import cn.orionsec.kit.lang.utils.io.FileType;
|
||||
import cn.orionsec.kit.lang.utils.io.Files1;
|
||||
import cn.orionsec.kit.net.host.sftp.SftpFile;
|
||||
@@ -41,6 +42,33 @@ public class SftpFileUtils {
|
||||
private SftpFileUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取移动目标路径
|
||||
*
|
||||
* @param source source
|
||||
* @param target target
|
||||
* @return absolute target
|
||||
*/
|
||||
public static String getAbsoluteTargetPath(String source, String target) {
|
||||
if (target.charAt(0) == Letters.SLASH) {
|
||||
// 绝对路径
|
||||
return Files1.getPath(Files1.normalize(target));
|
||||
} else {
|
||||
// 相对路径
|
||||
return Files1.getPath(Files1.normalize(Files1.getPath(source + "/../" + target)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分割文件路径
|
||||
*
|
||||
* @param path path
|
||||
* @return paths
|
||||
*/
|
||||
public static String[] fromMultiPaths(String path) {
|
||||
return path.split("\\|");
|
||||
}
|
||||
|
||||
/**
|
||||
* 转为文件
|
||||
*
|
||||
|
||||
@@ -3,4 +3,4 @@ VITE_API_BASE_URL=http://127.0.0.1:9200/orion-visor/api
|
||||
# websocket 路径
|
||||
VITE_WS_BASE_URL=ws://127.0.0.1:9200/orion-visor/keep-alive
|
||||
# 版本号
|
||||
VITE_APP_VERSION=2.4.0
|
||||
VITE_APP_VERSION=2.4.1
|
||||
|
||||
@@ -3,4 +3,4 @@ VITE_API_BASE_URL=/orion-visor/api
|
||||
# websocket 路径
|
||||
VITE_WS_BASE_URL=/orion-visor/keep-alive
|
||||
# 版本号
|
||||
VITE_APP_VERSION=2.4.0
|
||||
VITE_APP_VERSION=2.4.1
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "orion-visor-ui",
|
||||
"description": "Orion Visor UI",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"private": true,
|
||||
"author": "Jiahang Li",
|
||||
"license": "Apache 2.0",
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface HostRdpExtraSettingModel {
|
||||
authType: string;
|
||||
identityId: number;
|
||||
lowBandwidthMode: boolean;
|
||||
initialProgram: string;
|
||||
}
|
||||
|
||||
// 标签额外配置
|
||||
|
||||
@@ -15,7 +15,6 @@ export interface SystemSettingUpdateRequest {
|
||||
*/
|
||||
export interface AppInfoResponse {
|
||||
version: string;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
70
orion-visor-ui/src/api/terminal/terminal-file-log.ts
Normal file
70
orion-visor-ui/src/api/terminal/terminal-file-log.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { DataGrid, OrderDirection, Pagination } from '@/types/global';
|
||||
import type { TableData } from '@arco-design/web-vue';
|
||||
import axios from 'axios';
|
||||
import qs from 'query-string';
|
||||
|
||||
/**
|
||||
* 终端文件操作日志 查询请求
|
||||
*/
|
||||
export interface TerminalFileLogQueryRequest extends Pagination, OrderDirection {
|
||||
userId?: number;
|
||||
hostId?: number;
|
||||
type?: string;
|
||||
result?: number;
|
||||
startTimeRange?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 终端文件操作日志 查询响应
|
||||
*/
|
||||
export interface TerminalFileLogQueryResponse 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: TerminalFileLogExtra;
|
||||
}
|
||||
|
||||
/**
|
||||
* 终端文件操作日志 拓展对象
|
||||
*/
|
||||
export interface TerminalFileLogExtra {
|
||||
mod: number;
|
||||
target: string;
|
||||
maxCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询终端文件操作日志
|
||||
*/
|
||||
export function getTerminalFileLogPage(request: TerminalFileLogQueryRequest) {
|
||||
return axios.post<DataGrid<TerminalFileLogQueryResponse>>('/terminal/terminal-file-log/query', request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询终端文件操作日志数量
|
||||
*/
|
||||
export function getTerminalFileLogCount(request: TerminalFileLogQueryRequest) {
|
||||
return axios.post<number>('/terminal/terminal-file-log/count', request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除终端文件操作日志
|
||||
*/
|
||||
export function deleteTerminalFileLog(idList: Array<number>) {
|
||||
return axios.delete('/terminal/terminal-file-log/delete', {
|
||||
params: { idList },
|
||||
paramsSerializer: params => {
|
||||
return qs.stringify(params, { arrayFormat: 'comma' });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,74 +1,5 @@
|
||||
import type { DataGrid, OrderDirection, Pagination } from '@/types/global';
|
||||
import type { TableData } from '@arco-design/web-vue';
|
||||
import { httpBaseUrl } from '@/utils/env';
|
||||
import axios from 'axios';
|
||||
import qs from 'query-string';
|
||||
|
||||
/**
|
||||
* SFTP 操作日志 查询请求
|
||||
*/
|
||||
export interface TerminalSftpLogQueryRequest extends Pagination, OrderDirection {
|
||||
userId?: number;
|
||||
hostId?: number;
|
||||
type?: string;
|
||||
result?: number;
|
||||
startTimeRange?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* SFTP 操作日志 查询响应
|
||||
*/
|
||||
export interface TerminalSftpLogQueryResponse 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: TerminalSftpLogExtra;
|
||||
}
|
||||
|
||||
/**
|
||||
* SFTP 操作日志 拓展对象
|
||||
*/
|
||||
export interface TerminalSftpLogExtra {
|
||||
mod: number;
|
||||
target: string;
|
||||
maxCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询 SFTP 操作日志
|
||||
*/
|
||||
export function getTerminalSftpLogPage(request: TerminalSftpLogQueryRequest) {
|
||||
return axios.post<DataGrid<TerminalSftpLogQueryResponse>>('/terminal/terminal-sftp/query-log', request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 SFTP 操作日志数量
|
||||
*/
|
||||
export function getTerminalSftpLogCount(request: TerminalSftpLogQueryRequest) {
|
||||
return axios.post<number>('/terminal/terminal-sftp/log-count', request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 SFTP 操作日志
|
||||
*/
|
||||
export function deleteTerminalSftpLog(idList: Array<number>) {
|
||||
return axios.delete('/terminal/terminal-sftp/delete-log', {
|
||||
params: { idList },
|
||||
paramsSerializer: params => {
|
||||
return qs.stringify(params, { arrayFormat: 'comma' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SFTP 文件内容
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { MenuQueryResponse } from '@/api/system/menu';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import axios from 'axios';
|
||||
|
||||
/**
|
||||
@@ -35,7 +36,9 @@ export interface UserUpdatePasswordResponse {
|
||||
* 获取用户聚合信息
|
||||
*/
|
||||
export function getUserAggregateInfo() {
|
||||
return axios.get<UserAggregateResponse>('/infra/user-aggregate/user');
|
||||
return axios.get<AxiosResponse<UserAggregateResponse>>('/infra/user-aggregate/user', {
|
||||
unwrap: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -331,7 +331,7 @@
|
||||
// 查询未读消息
|
||||
pullHasUnreadMessage();
|
||||
// 注册未读消息轮询
|
||||
messageIntervalId.value = setInterval(pullHasUnreadMessage, 30000);
|
||||
messageIntervalId.value = window.setInterval(pullHasUnreadMessage, 30000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
// 等待一秒后先查询一下状态
|
||||
setTimeout(pullExecStatus, 1000);
|
||||
// 注册状态轮询
|
||||
pullIntervalId.value = setInterval(pullExecStatus, 5000);
|
||||
pullIntervalId.value = window.setInterval(pullExecStatus, 5000);
|
||||
}
|
||||
// 打开日志
|
||||
nextTick(() => {
|
||||
|
||||
@@ -172,11 +172,11 @@ export default class LogAppender implements ILogAppender {
|
||||
};
|
||||
this.client.onmessage = this.processMessage.bind(this);
|
||||
// 注册持久化
|
||||
this.keepAliveTask = setInterval(() => {
|
||||
this.keepAliveTask = window.setInterval(() => {
|
||||
if (this.client?.readyState === WebSocket.OPEN) {
|
||||
this.client?.send('p');
|
||||
}
|
||||
}, 15000) as unknown as number;
|
||||
}, 15000);
|
||||
}
|
||||
|
||||
// 打开日志
|
||||
|
||||
@@ -4,7 +4,7 @@ export const MessageStatus = {
|
||||
READ: 1,
|
||||
};
|
||||
|
||||
export const MESSAGE_CONFIG_KEY = 'messageConfig';
|
||||
export const MESSAGE_CONFIG_KEY = 'message-config';
|
||||
|
||||
// 查询数量
|
||||
export const messageLimit = 15;
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { FileItem } from '@arco-design/web-vue';
|
||||
import type { IFileUploader, ResponseMessageBody } from './const';
|
||||
import { UploadOperatorType, UploadReceiverType } from './const';
|
||||
import { openFileUploadChannel } from '@/api/system/upload';
|
||||
import { closeFileReader } from '@/utils/file';
|
||||
|
||||
// 512 KB
|
||||
export const PART_SIZE = 512 * 1024;
|
||||
@@ -81,13 +82,14 @@ export default class FileUploader implements IFileUploader {
|
||||
|
||||
// 上传下一块数据
|
||||
private async uploadNextPart() {
|
||||
let reader = undefined as unknown as FileReader;
|
||||
try {
|
||||
if (this.currentPart < this.totalPart) {
|
||||
// 有下一个分片则上传
|
||||
const start = this.currentPart++ * PART_SIZE;
|
||||
const end = Math.min(this.currentFile.size, start + PART_SIZE);
|
||||
const chunk = this.currentFile.slice(start, end);
|
||||
const reader = new FileReader();
|
||||
reader = new FileReader();
|
||||
// 读取数据
|
||||
const arrayBuffer = await new Promise((resolve, reject) => {
|
||||
reader.onload = () => resolve(reader.result);
|
||||
@@ -107,11 +109,15 @@ export default class FileUploader implements IFileUploader {
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
// 读取文件失败
|
||||
// 发送读取文件失败
|
||||
this.client?.send(JSON.stringify({
|
||||
type: UploadOperatorType.ERROR,
|
||||
fileId: this.currentFileItem.uid,
|
||||
}));
|
||||
// 释放资源
|
||||
if (reader) {
|
||||
closeFileReader(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,19 +8,19 @@ const ASSET_AUDIT: AppRouteRecordRaw[] = [
|
||||
component: DEFAULT_LAYOUT,
|
||||
children: [
|
||||
{
|
||||
name: 'connectLog',
|
||||
path: '/audit/connect-log',
|
||||
component: () => import('@/views/asset-audit/connect-log/index.vue'),
|
||||
name: 'terminalConnectLog',
|
||||
path: '/audit/terminal-connect-log',
|
||||
component: () => import('@/views/asset-audit/terminal-connect-log/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'connectSession',
|
||||
path: '/audit/connect-session',
|
||||
component: () => import('@/views/asset-audit/connect-session/index.vue'),
|
||||
name: 'terminalConnectSession',
|
||||
path: '/audit/terminal-connect-session',
|
||||
component: () => import('@/views/asset-audit/terminal-connect-session/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'sftpLog',
|
||||
path: '/audit/sftp-log',
|
||||
component: () => import('@/views/asset-audit/sftp-log/index.vue'),
|
||||
name: 'terminalFileLog',
|
||||
path: '/audit/terminal-file-log',
|
||||
component: () => import('@/views/asset-audit/terminal-file-log/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type {
|
||||
TerminalInteractSetting,
|
||||
TerminalPluginsSetting,
|
||||
TerminalPreference,
|
||||
TerminalRdpActionBarSetting,
|
||||
TerminalRdpGraphSetting,
|
||||
TerminalSessionSetting,
|
||||
TerminalRdpSessionSetting,
|
||||
TerminalShortcutSetting,
|
||||
TerminalSshActionBarSetting,
|
||||
TerminalSshDisplaySetting,
|
||||
TerminalSshInteractSetting,
|
||||
TerminalSshPluginsSetting,
|
||||
TerminalState
|
||||
} from './types';
|
||||
import type {
|
||||
@@ -34,30 +34,30 @@ import { TerminalSessionTypes, TerminalTabs } from '@/views/terminal/types/const
|
||||
import TerminalTabManager from '@/views/terminal/service/tab/terminal-tab-manager';
|
||||
import TerminalPanelManager from '@/views/terminal/service/tab/terminal-panel-manager';
|
||||
import TerminalSessionManager from '@/views/terminal/service/session/terminal-session-manager';
|
||||
import SftpTransferManager from '@/views/terminal/service/transfer/sftp-transfer-manager';
|
||||
import TerminalTransferManager from '@/views/terminal/service/transfer/terminal-transfer-manager';
|
||||
|
||||
// 终端偏好项
|
||||
export const TerminalPreferenceItem = {
|
||||
// 新建连接类型
|
||||
NEW_CONNECTION_TYPE: 'newConnectionType',
|
||||
// 终端主题
|
||||
THEME: 'theme',
|
||||
// ssh 主题
|
||||
SSH_THEME: 'sshTheme',
|
||||
// ssh 显示设置
|
||||
SSH_DISPLAY_SETTING: 'sshDisplaySetting',
|
||||
// rdp 图形化设置
|
||||
RDP_GRAPH_SETTING: 'rdpGraphSetting',
|
||||
// ssh 操作栏设置
|
||||
SSH_ACTION_BAR_SETTING: 'sshActionBarSetting',
|
||||
// ssh 右键菜单设置
|
||||
SSH_RIGHT_MENU_SETTING: 'sshRightMenuSetting',
|
||||
// ssh 交互设置
|
||||
SSH_INTERACT_SETTING: 'sshInteractSetting',
|
||||
// ssh 插件设置
|
||||
SSH_PLUGINS_SETTING: 'sshPluginsSetting',
|
||||
// rdp 图形化设置
|
||||
RDP_GRAPH_SETTING: 'rdpGraphSetting',
|
||||
// rdp 操作栏设置
|
||||
RDP_ACTION_BAR_SETTING: 'rdpActionBarSetting',
|
||||
// 右键菜单设置
|
||||
RIGHT_MENU_SETTING: 'rightMenuSetting',
|
||||
// 交互设置
|
||||
INTERACT_SETTING: 'interactSetting',
|
||||
// 插件设置
|
||||
PLUGINS_SETTING: 'pluginsSetting',
|
||||
// 会话设置
|
||||
SESSION_SETTING: 'sessionSetting',
|
||||
RDP_SESSION_SETTING: 'rdpSessionSetting',
|
||||
// 快捷键设置
|
||||
SHORTCUT_SETTING: 'shortcutSetting',
|
||||
};
|
||||
@@ -66,17 +66,17 @@ export default defineStore('terminal', {
|
||||
state: (): TerminalState => ({
|
||||
preference: {
|
||||
newConnectionType: 'group',
|
||||
theme: {
|
||||
sshTheme: {
|
||||
schema: {} as TerminalThemeSchema
|
||||
} as TerminalTheme,
|
||||
sshDisplaySetting: {} as TerminalSshDisplaySetting,
|
||||
rdpGraphSetting: {} as TerminalRdpGraphSetting,
|
||||
sshActionBarSetting: {} as TerminalSshActionBarSetting,
|
||||
sshRightMenuSetting: [],
|
||||
sshInteractSetting: {} as TerminalSshInteractSetting,
|
||||
sshPluginsSetting: {} as TerminalSshPluginsSetting,
|
||||
rdpGraphSetting: {} as TerminalRdpGraphSetting,
|
||||
rdpSessionSetting: {} as TerminalRdpSessionSetting,
|
||||
rdpActionBarSetting: {} as TerminalRdpActionBarSetting,
|
||||
rightMenuSetting: [],
|
||||
interactSetting: {} as TerminalInteractSetting,
|
||||
pluginsSetting: {} as TerminalPluginsSetting,
|
||||
sessionSetting: {} as TerminalSessionSetting,
|
||||
shortcutSetting: {
|
||||
enabled: false,
|
||||
keys: []
|
||||
@@ -90,7 +90,7 @@ export default defineStore('terminal', {
|
||||
tabManager: new TerminalTabManager(),
|
||||
panelManager: new TerminalPanelManager(),
|
||||
sessionManager: markRaw(new TerminalSessionManager()),
|
||||
transferManager: new SftpTransferManager(),
|
||||
transferManager: new TerminalTransferManager(),
|
||||
}),
|
||||
|
||||
actions: {
|
||||
@@ -100,11 +100,11 @@ export default defineStore('terminal', {
|
||||
// 加载偏好
|
||||
const { data } = await getPreference<TerminalPreference>('TERMINAL');
|
||||
// theme 不存在则默认加载第一个
|
||||
if (!data.theme?.name) {
|
||||
if (!data.sshTheme?.name) {
|
||||
const { data: themes } = await getTerminalThemes();
|
||||
data.theme = themes[0];
|
||||
data.sshTheme = themes[0];
|
||||
// 更新默认主题偏好
|
||||
await this.updateTerminalPreference(TerminalPreferenceItem.THEME, data.theme);
|
||||
await this.updateTerminalPreference(TerminalPreferenceItem.SSH_THEME, data.sshTheme);
|
||||
}
|
||||
// 移除禁用的快捷键
|
||||
if (data.shortcutSetting?.enabled) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ISftpTransferManager, ITerminalPanelManager, ITerminalSessionManager, ITerminalTabManager, TerminalTheme } from '@/views/terminal/interfaces';
|
||||
import type { ITerminalPanelManager, ITerminalSessionManager, ITerminalTabManager, ITerminalTransferManager, TerminalTheme } from '@/views/terminal/interfaces';
|
||||
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
||||
|
||||
export interface TerminalState {
|
||||
@@ -8,21 +8,21 @@ export interface TerminalState {
|
||||
tabManager: ITerminalTabManager;
|
||||
panelManager: ITerminalPanelManager;
|
||||
sessionManager: ITerminalSessionManager;
|
||||
transferManager: ISftpTransferManager;
|
||||
transferManager: ITerminalTransferManager;
|
||||
}
|
||||
|
||||
// 终端配置
|
||||
export interface TerminalPreference {
|
||||
newConnectionType: string;
|
||||
theme: TerminalTheme;
|
||||
sshTheme: TerminalTheme;
|
||||
sshDisplaySetting: TerminalSshDisplaySetting;
|
||||
rdpGraphSetting: TerminalRdpGraphSetting;
|
||||
sshActionBarSetting: TerminalSshActionBarSetting;
|
||||
sshRightMenuSetting: Array<string>,
|
||||
sshInteractSetting: TerminalSshInteractSetting;
|
||||
sshPluginsSetting: TerminalSshPluginsSetting;
|
||||
rdpGraphSetting: TerminalRdpGraphSetting;
|
||||
rdpActionBarSetting: TerminalRdpActionBarSetting;
|
||||
rightMenuSetting: Array<string>,
|
||||
interactSetting: TerminalInteractSetting;
|
||||
pluginsSetting: TerminalPluginsSetting;
|
||||
sessionSetting: TerminalSessionSetting;
|
||||
rdpSessionSetting: TerminalRdpSessionSetting;
|
||||
shortcutSetting: TerminalShortcutSetting;
|
||||
}
|
||||
|
||||
@@ -38,13 +38,44 @@ export interface TerminalSshDisplaySetting {
|
||||
cursorBlink?: boolean;
|
||||
}
|
||||
|
||||
// SSH 操作栏设置
|
||||
export interface TerminalSshActionBarSetting {
|
||||
connectStatus?: boolean;
|
||||
share?: boolean;
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// SSH 插件设置
|
||||
export interface TerminalSshPluginsSetting {
|
||||
enableWeblinkPlugin: boolean;
|
||||
enableWebglPlugin: boolean;
|
||||
enableUnicodePlugin: boolean;
|
||||
enableImagePlugin: boolean;
|
||||
}
|
||||
|
||||
// SSH 交互设置
|
||||
export interface TerminalSshInteractSetting {
|
||||
fastScrollModifier: boolean;
|
||||
altClickMovesCursor: boolean;
|
||||
rightClickSelectsWord: boolean;
|
||||
selectionChangeCopy: boolean;
|
||||
copyAutoTrim: boolean;
|
||||
pasteAutoTrim: boolean;
|
||||
rightClickPaste: boolean;
|
||||
enableRightClickMenu: boolean;
|
||||
enableBell: boolean;
|
||||
wordSeparator: string;
|
||||
terminalEmulationType: string;
|
||||
scrollBackLine: number;
|
||||
}
|
||||
|
||||
// RDP 图形化设置
|
||||
export interface TerminalRdpGraphSetting {
|
||||
displaySize?: string;
|
||||
displayWidth?: number;
|
||||
displayHeight?: number;
|
||||
enableAudioInput?: boolean;
|
||||
enableAudioOutput?: boolean;
|
||||
|
||||
colorDepth?: number;
|
||||
forceLossless?: boolean;
|
||||
enableWallpaper?: boolean;
|
||||
@@ -56,13 +87,7 @@ export interface TerminalRdpGraphSetting {
|
||||
disableBitmapCaching?: boolean;
|
||||
disableOffscreenCaching?: boolean;
|
||||
disableGlyphCaching?: boolean;
|
||||
}
|
||||
|
||||
// SSH 操作栏设置
|
||||
export interface TerminalSshActionBarSetting {
|
||||
connectStatus?: boolean;
|
||||
|
||||
[key: string]: unknown;
|
||||
disableGfx?: boolean;
|
||||
}
|
||||
|
||||
// RDP 操作栏设置
|
||||
@@ -79,32 +104,11 @@ export interface TerminalRdpActionBarSetting {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// 交互设置
|
||||
export interface TerminalInteractSetting {
|
||||
fastScrollModifier: boolean;
|
||||
altClickMovesCursor: boolean;
|
||||
rightClickSelectsWord: boolean;
|
||||
selectionChangeCopy: boolean;
|
||||
copyAutoTrim: boolean;
|
||||
pasteAutoTrim: boolean;
|
||||
rightClickPaste: boolean;
|
||||
enableRightClickMenu: boolean;
|
||||
enableBell: boolean;
|
||||
wordSeparator: string;
|
||||
}
|
||||
|
||||
// 插件设置
|
||||
export interface TerminalPluginsSetting {
|
||||
enableWeblinkPlugin: boolean;
|
||||
enableWebglPlugin: boolean;
|
||||
enableUnicodePlugin: boolean;
|
||||
enableImagePlugin: boolean;
|
||||
}
|
||||
|
||||
// 会话设置
|
||||
export interface TerminalSessionSetting {
|
||||
terminalEmulationType: string;
|
||||
scrollBackLine: number;
|
||||
// RDP 会话设置
|
||||
export interface TerminalRdpSessionSetting {
|
||||
enableAudioInput?: boolean;
|
||||
enableAudioOutput?: boolean;
|
||||
driveMountMode?: string;
|
||||
}
|
||||
|
||||
// 终端快捷键设置
|
||||
|
||||
@@ -8,6 +8,41 @@ import { removeRouteListener } from '@/utils/route-listener';
|
||||
import { getUserAggregateInfo } from '@/api/user/user-aggregate';
|
||||
import { useAppStore, useCacheStore, useMenuStore, useTabBarStore, useTipsStore } from '@/store';
|
||||
|
||||
const CHECK_APP_VERSION_KEY = 'check-app-version';
|
||||
|
||||
// 检查版本更新
|
||||
const checkForVersionUpdate = (serverVersion: string) => {
|
||||
try {
|
||||
if (!serverVersion) {
|
||||
return;
|
||||
}
|
||||
const clientVersion = import.meta.env.VITE_APP_VERSION;
|
||||
// 版本相同
|
||||
if (serverVersion === clientVersion) {
|
||||
localStorage.removeItem(CHECK_APP_VERSION_KEY);
|
||||
return;
|
||||
}
|
||||
// 版本不同
|
||||
const lastCheck = localStorage.getItem(CHECK_APP_VERSION_KEY);
|
||||
const lastCheckData = lastCheck ? JSON.parse(lastCheck) : null;
|
||||
// 判断是否是同版本 或 距离上次提醒不超过 24 小时
|
||||
if (lastCheckData?.version === serverVersion && Date.now() - (lastCheckData?.time || 0) < 24 * 60 * 60 * 1000) {
|
||||
return;
|
||||
}
|
||||
// 提示用户更新
|
||||
if (window.confirm('检测到新版本, 是否刷新页面以获取最新内容?')) {
|
||||
window.location.reload();
|
||||
}
|
||||
// 更新 localStorage 记录
|
||||
localStorage.setItem(CHECK_APP_VERSION_KEY, JSON.stringify({
|
||||
version: serverVersion,
|
||||
time: Date.now(),
|
||||
}));
|
||||
} catch (error) {
|
||||
// ignored
|
||||
}
|
||||
};
|
||||
|
||||
export default defineStore('user', {
|
||||
state: (): UserState => ({
|
||||
id: undefined,
|
||||
@@ -32,7 +67,9 @@ export default defineStore('user', {
|
||||
|
||||
// 获取用户信息
|
||||
async getUserInfo() {
|
||||
const { data } = await getUserAggregateInfo();
|
||||
const { data: { data }, headers } = await getUserAggregateInfo();
|
||||
// 检查版本更新
|
||||
checkForVersionUpdate(headers?.['x-app-version']);
|
||||
// 设置用户信息
|
||||
this.setUserInfo({
|
||||
id: data.user.id,
|
||||
|
||||
@@ -41,6 +41,17 @@ export function readFileText(e: File, encoding = 'UTF-8'): Promise<string> {
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭 fileReader
|
||||
export function closeFileReader(reader: FileReader) {
|
||||
// 清理资源
|
||||
if (reader.readyState === FileReader.LOADING) {
|
||||
reader.abort();
|
||||
}
|
||||
reader.onload = null;
|
||||
reader.onerror = null;
|
||||
reader.onabort = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析路径类型
|
||||
*/
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// 表名称
|
||||
export const TableName = 'sftp-log';
|
||||
|
||||
// sftp 操作类型
|
||||
export const SftpOperatorType = {
|
||||
SFTP_MOVE: 'terminal:sftp-move',
|
||||
SFTP_CHMOD: 'terminal:sftp-chmod',
|
||||
};
|
||||
|
||||
// 最大展示数量
|
||||
export const showPathMaxCount = 5;
|
||||
|
||||
// sftp 操作类型 字典项
|
||||
export const sftpOperatorTypeKey = 'sftpOperatorType';
|
||||
|
||||
// sftp 操作结果 字典项
|
||||
export const sftpOperatorResultKey = 'operatorLogResult';
|
||||
|
||||
// 加载的字典值
|
||||
export const dictKeys = [sftpOperatorTypeKey, sftpOperatorResultKey];
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'connectLog'
|
||||
name: 'terminalConnectLog'
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<div class="table-left-bar-handle">
|
||||
<!-- 标题 -->
|
||||
<div class="table-title">
|
||||
主机在线会话
|
||||
终端在线会话
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'connectSession'
|
||||
name: 'terminalConnectSession'
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
import ConnectSessionTable from './components/connect-session-table.vue';
|
||||
|
||||
const render = ref(false);
|
||||
const eventDrawer = ref();
|
||||
|
||||
// 加载字典配置
|
||||
onBeforeMount(async () => {
|
||||
@@ -24,14 +24,14 @@
|
||||
<a-form-item field="type" label="操作类型">
|
||||
<a-select v-model="formModel.type"
|
||||
placeholder="请选择类型"
|
||||
:options="toOptions(sftpOperatorTypeKey)"
|
||||
:options="toOptions(terminalFileOperatorTypeKey)"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 执行结果 -->
|
||||
<a-form-item field="result" label="执行结果">
|
||||
<a-select v-model="formModel.result"
|
||||
placeholder="请选择执行结果"
|
||||
:options="toOptions(sftpOperatorResultKey)"
|
||||
:options="toOptions(operatorResultKey)"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 开始时间 -->
|
||||
@@ -50,7 +50,7 @@
|
||||
<div class="table-left-bar-handle">
|
||||
<!-- 标题 -->
|
||||
<div class="table-title">
|
||||
文件操作日志
|
||||
终端文件日志
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧操作 -->
|
||||
@@ -61,7 +61,7 @@
|
||||
position="br"
|
||||
type="warning"
|
||||
@ok="deleteSelectedRows">
|
||||
<a-button v-permission="['infra:operator-log:delete', 'terminal:terminal-sftp-log:management:delete']"
|
||||
<a-button v-permission="['infra:operator-log:delete', 'terminal:terminal-file-log:management:delete']"
|
||||
type="primary"
|
||||
status="danger"
|
||||
:disabled="selectedKeys.length === 0">
|
||||
@@ -110,7 +110,7 @@
|
||||
<!-- 操作类型 -->
|
||||
<template #type="{ record }">
|
||||
<a-tag color="arcoblue">
|
||||
{{ getDictValue(sftpOperatorTypeKey, record.type) }}
|
||||
{{ getDictValue(terminalFileOperatorTypeKey, record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<!-- 文件数量 -->
|
||||
@@ -127,11 +127,11 @@
|
||||
{{ path }}
|
||||
</span>
|
||||
<!-- 移动目标路径 -->
|
||||
<span class="table-cell-sub-value" v-if="SftpOperatorType.SFTP_MOVE === record.type">
|
||||
<span class="table-cell-sub-value" v-if="TerminalFileOperatorType.SFTP_MOVE === record.type">
|
||||
移动到 {{ record.extra?.target }}
|
||||
</span>
|
||||
<!-- 提权信息 -->
|
||||
<span class="table-cell-sub-value" v-if="SftpOperatorType.SFTP_CHMOD === record.type">
|
||||
<span class="table-cell-sub-value" v-if="TerminalFileOperatorType.SFTP_CHMOD === record.type">
|
||||
提权 {{ record.extra?.mod }} {{ permission10toString(record.extra?.mod as number) }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -145,8 +145,8 @@
|
||||
</template>
|
||||
<!-- 执行结果 -->
|
||||
<template #result="{ record }">
|
||||
<a-tag :color="getDictValue(sftpOperatorResultKey, record.result, 'color')">
|
||||
{{ getDictValue(sftpOperatorResultKey, record.result) }}
|
||||
<a-tag :color="getDictValue(operatorResultKey, record.result, 'color')">
|
||||
{{ getDictValue(operatorResultKey, record.result) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<!-- 留痕地址 -->
|
||||
@@ -169,7 +169,7 @@
|
||||
position="left"
|
||||
type="warning"
|
||||
@ok="deleteRow(record)">
|
||||
<a-button v-permission="['infra:operator-log:delete', 'terminal:terminal-sftp-log:management:delete']"
|
||||
<a-button v-permission="['infra:operator-log:delete', 'terminal:terminal-file-log:management:delete']"
|
||||
type="text"
|
||||
size="mini"
|
||||
status="danger">
|
||||
@@ -184,15 +184,15 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'sftpLogTable'
|
||||
name: 'fileLogTable'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { TerminalSftpLogQueryRequest, TerminalSftpLogQueryResponse } from '@/api/terminal/terminal-sftp';
|
||||
import type { TerminalFileLogQueryRequest, TerminalFileLogQueryResponse } from '@/api/terminal/terminal-file-log';
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { getTerminalSftpLogPage, deleteTerminalSftpLog } from '@/api/terminal/terminal-sftp';
|
||||
import { TableName, sftpOperatorTypeKey, sftpOperatorResultKey, SftpOperatorType, showPathMaxCount } from '../types/const';
|
||||
import { getTerminalFileLogPage, deleteTerminalFileLog } from '@/api/terminal/terminal-file-log';
|
||||
import { TableName, terminalFileOperatorTypeKey, operatorResultKey, TerminalFileOperatorType, showPathMaxCount } from '../types/const';
|
||||
import { useTablePagination, useRowSelection, useTableColumns } from '@/hooks/table';
|
||||
import { useDictStore } from '@/store';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
@@ -212,9 +212,9 @@
|
||||
const { loading, setLoading } = useLoading();
|
||||
const { toOptions, getDictValue } = useDictStore();
|
||||
|
||||
const tableRenderData = ref<Array<TerminalSftpLogQueryResponse>>([]);
|
||||
const tableRenderData = ref<Array<TerminalFileLogQueryResponse>>([]);
|
||||
const selectedKeys = ref<Array<number>>([]);
|
||||
const formModel = reactive<TerminalSftpLogQueryRequest>({
|
||||
const formModel = reactive<TerminalFileLogQueryRequest>({
|
||||
userId: undefined,
|
||||
hostId: undefined,
|
||||
type: undefined,
|
||||
@@ -223,11 +223,11 @@
|
||||
});
|
||||
|
||||
// 加载数据
|
||||
const doFetchTableData = async (request: TerminalSftpLogQueryRequest) => {
|
||||
const doFetchTableData = async (request: TerminalFileLogQueryRequest) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// 查询
|
||||
const { data } = await getTerminalSftpLogPage(queryOrder.markOrderly(request));
|
||||
const { data } = await getTerminalFileLogPage(queryOrder.markOrderly(request));
|
||||
// 设置最大数量
|
||||
data.rows.forEach(s => {
|
||||
s.extra.maxCount = showPathMaxCount;
|
||||
@@ -253,7 +253,7 @@
|
||||
try {
|
||||
setLoading(true);
|
||||
// 调用删除接口
|
||||
await deleteTerminalSftpLog(selectedKeys.value);
|
||||
await deleteTerminalFileLog(selectedKeys.value);
|
||||
Message.success(`成功删除 ${selectedKeys.value.length} 条数据`);
|
||||
selectedKeys.value = [];
|
||||
// 重新加载
|
||||
@@ -265,11 +265,11 @@
|
||||
};
|
||||
|
||||
// 删除当前行
|
||||
const deleteRow = async (record: TerminalSftpLogQueryResponse) => {
|
||||
const deleteRow = async (record: TerminalFileLogQueryResponse) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// 调用删除接口
|
||||
await deleteTerminalSftpLog([record.id]);
|
||||
await deleteTerminalFileLog([record.id]);
|
||||
Message.success('删除成功');
|
||||
selectedKeys.value = [];
|
||||
// 重新加载
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="layout-container" v-if="render">
|
||||
<!-- 列表-表格 -->
|
||||
<sftp-log-table />
|
||||
<file-log-table />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'sftpLog'
|
||||
name: 'terminalFileLog'
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import { ref, onBeforeMount } from 'vue';
|
||||
import { useDictStore } from '@/store';
|
||||
import { dictKeys } from './types/const';
|
||||
import SftpLogTable from './components/sftp-log-table.vue';
|
||||
import FileLogTable from './components/file-log-table.vue';
|
||||
|
||||
const render = ref(false);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// 表名称
|
||||
export const TableName = 'file-log';
|
||||
|
||||
// 终端文件操作类型
|
||||
export const TerminalFileOperatorType = {
|
||||
SFTP_MOVE: 'terminal:sftp-move',
|
||||
SFTP_CHMOD: 'terminal:sftp-chmod',
|
||||
};
|
||||
|
||||
// 最大展示数量
|
||||
export const showPathMaxCount = 5;
|
||||
|
||||
// 终端文件操作类型 字典项
|
||||
export const terminalFileOperatorTypeKey = 'terminalFileOperatorType';
|
||||
|
||||
// 操作结果 字典项
|
||||
export const operatorResultKey = 'operatorLogResult';
|
||||
|
||||
// 加载的字典值
|
||||
export const dictKeys = [terminalFileOperatorTypeKey, operatorResultKey];
|
||||
@@ -30,7 +30,7 @@ const columns = [
|
||||
title: '操作类型',
|
||||
dataIndex: 'type',
|
||||
slotName: 'type',
|
||||
width: 116,
|
||||
width: 146,
|
||||
align: 'left',
|
||||
default: true,
|
||||
}, {
|
||||
@@ -133,6 +133,7 @@
|
||||
|
||||
// 更新主机信息
|
||||
const onUpdateHostInfo = (id: number) => {
|
||||
title.value = '修改主机';
|
||||
hostId.value = id;
|
||||
hostViewUpdated.value = true;
|
||||
};
|
||||
|
||||
@@ -194,11 +194,13 @@
|
||||
// 复制
|
||||
const { data } = await copyHost(formModel.value);
|
||||
Message.success('复制成功');
|
||||
formModel.value.id = data;
|
||||
emits('updated', data);
|
||||
} else if (!formModel.value.id) {
|
||||
// 新增
|
||||
const { data } = await createHost(formModel.value);
|
||||
Message.success('创建成功');
|
||||
formModel.value.id = data;
|
||||
emits('updated', data);
|
||||
} else {
|
||||
// 修改
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
<!-- RDP版本 -->
|
||||
<a-form-item field="versionGt81"
|
||||
label="RDP版本"
|
||||
tooltip="RDP 8.1 及以上版本支持动态调整分辨率"
|
||||
hide-asterisk>
|
||||
<a-switch v-model="formModel.versionGt81"
|
||||
type="round"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<!-- 跳转 -->
|
||||
<span class="pointer span-blue"
|
||||
title="详情"
|
||||
@click="router.push({ name: 'connectLog', query: { action: 'self' } })">
|
||||
@click="router.push({ name: 'terminalConnectLog', query: { action: 'self' } })">
|
||||
详情
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
background: isDark.value ? '#354276' : '#E8F3FF',
|
||||
iconColor: isDark.value ? '#4A7FF7' : '#165DFF',
|
||||
},
|
||||
go: () => router.push({ name: 'connectLog', query: { action: 'self' } })
|
||||
go: () => router.push({ name: 'terminalConnectLog', query: { action: 'self' } })
|
||||
}, {
|
||||
title: '今日批量执行次数',
|
||||
value: props.data.exec?.todayExecCommandCount || 0,
|
||||
|
||||
@@ -278,7 +278,7 @@
|
||||
|
||||
// 设置轮询状态
|
||||
onMounted(() => {
|
||||
pullIntervalId.value = setInterval(pullTaskStatus, 5000);
|
||||
pullIntervalId.value = window.setInterval(pullTaskStatus, 5000);
|
||||
});
|
||||
|
||||
// 卸载状态查询
|
||||
|
||||
@@ -429,7 +429,7 @@
|
||||
// 加载数据
|
||||
fetchTableData();
|
||||
// 注册状态轮询
|
||||
pullIntervalId.value = setInterval(pullExecStatus, 10000);
|
||||
pullIntervalId.value = window.setInterval(pullExecStatus, 10000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -417,7 +417,7 @@
|
||||
// 加载数据
|
||||
fetchTableData();
|
||||
// 注册状态轮询
|
||||
pullIntervalId.value = setInterval(pullJobStatus, 10000);
|
||||
pullIntervalId.value = window.setInterval(pullJobStatus, 10000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user