Merge pull request #115 from dromara/dev

Dev
This commit is contained in:
李佳航
2025-07-02 13:20:08 +08:00
committed by GitHub
170 changed files with 4177 additions and 1835 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -1,4 +1,4 @@
FROM guacamole/guacd:1.5.5
FROM guacamole/guacd:1.6.0
USER root
# 系统时区
ARG TZ=Asia/Shanghai

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -36,7 +36,7 @@ public interface AppConst extends OrionConst {
/**
* 同 ${orion.version} 迭代时候需要手动更改
*/
String VERSION = "2.4.0";
String VERSION = "2.4.1";
/**
* 同 ${spring.application.name}

View File

@@ -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";
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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();

View File

@@ -56,4 +56,9 @@ public class HostRdpExtraModel implements GenericsDataModel {
*/
private Boolean lowBandwidthMode;
/**
* 初始化程序
*/
private String initialProgram;
}

View File

@@ -37,6 +37,14 @@ import java.util.List;
*/
public interface HostConfigService {
/**
* 初始化主机配置
*
* @param hostId hostId
* @param types types
*/
void initHostConfig(Long hostId, List<String> types);
/**
* 更新主机配置
*

View File

@@ -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);

View File

@@ -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)) {

View File

@@ -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;

View File

@@ -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)));
}
}
}

View File

@@ -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")

View File

@@ -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();
}

View File

@@ -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)

View File

@@ -49,7 +49,4 @@ public class AppInfoVO implements Serializable {
@Schema(description = "系统版本")
private String version;
@Schema(description = "机器码")
private String uuid;
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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 = "获取文件内容")

View File

@@ -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);
}

View File

@@ -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> 条"),
};
}
}

View File

@@ -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>"),
};
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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";
/**
* 远程应用名称
*/

View File

@@ -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),
;

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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())

View File

@@ -126,6 +126,16 @@ public class TerminalChannelExtra {
*/
private Boolean disableGlyphCaching;
/**
* 禁用图形加速
*/
private Boolean disableGfx;
/**
* 驱动挂载模式
*/
private String driveMountMode;
// -------------------- vnc --------------------
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -41,6 +41,11 @@ import lombok.experimental.SuperBuilder;
@AllArgsConstructor
public class TerminalSetInfo implements IJsonObject {
/**
* logId
*/
private Long logId;
/**
* 地址
*/

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
/**
* 错误信息 后端赋值
*/

View File

@@ -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();
// 检查文件是否存在

View File

@@ -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);
}

View File

@@ -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();
// 检查文件是否存在

View File

@@ -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);
}

View File

@@ -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);
/**
* 设置文件内容
*

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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("\\|");
}
/**
* 转为文件
*

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -30,6 +30,7 @@ export interface HostRdpExtraSettingModel {
authType: string;
identityId: number;
lowBandwidthMode: boolean;
initialProgram: string;
}
// 标签额外配置

View File

@@ -15,7 +15,6 @@ export interface SystemSettingUpdateRequest {
*/
export interface AppInfoResponse {
version: string;
uuid: string;
}
/**

View 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' });
}
});
}

View File

@@ -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 文件内容

View File

@@ -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
});
}
/**

View File

@@ -331,7 +331,7 @@
// 查询未读消息
pullHasUnreadMessage();
// 注册未读消息轮询
messageIntervalId.value = setInterval(pullHasUnreadMessage, 30000);
messageIntervalId.value = window.setInterval(pullHasUnreadMessage, 30000);
});
onUnmounted(() => {

View File

@@ -69,7 +69,7 @@
// 等待一秒后先查询一下状态
setTimeout(pullExecStatus, 1000);
// 注册状态轮询
pullIntervalId.value = setInterval(pullExecStatus, 5000);
pullIntervalId.value = window.setInterval(pullExecStatus, 5000);
}
// 打开日志
nextTick(() => {

View File

@@ -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);
}
// 打开日志

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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'),
},
],
},

View File

@@ -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) {

View File

@@ -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;
}
// 终端快捷键设置

View File

@@ -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,

View File

@@ -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;
}
/**
* 解析路径类型
*/

View File

@@ -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];

View File

@@ -14,7 +14,7 @@
<script lang="ts">
export default {
name: 'connectLog'
name: 'terminalConnectLog'
};
</script>

View File

@@ -46,7 +46,7 @@
<div class="table-left-bar-handle">
<!-- 标题 -->
<div class="table-title">
主机在线会话
终端在线会话
</div>
</div>
</template>

View File

@@ -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 () => {

View File

@@ -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 = [];
//

View File

@@ -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);

View File

@@ -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];

View File

@@ -30,7 +30,7 @@ const columns = [
title: '操作类型',
dataIndex: 'type',
slotName: 'type',
width: 116,
width: 146,
align: 'left',
default: true,
}, {

View File

@@ -133,6 +133,7 @@
// 更新主机信息
const onUpdateHostInfo = (id: number) => {
title.value = '修改主机';
hostId.value = id;
hostViewUpdated.value = true;
};

View File

@@ -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 {
// 修改

View File

@@ -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"

View File

@@ -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>

View File

@@ -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,

View File

@@ -278,7 +278,7 @@
// 设置轮询状态
onMounted(() => {
pullIntervalId.value = setInterval(pullTaskStatus, 5000);
pullIntervalId.value = window.setInterval(pullTaskStatus, 5000);
});
// 卸载状态查询

View File

@@ -429,7 +429,7 @@
// 加载数据
fetchTableData();
// 注册状态轮询
pullIntervalId.value = setInterval(pullExecStatus, 10000);
pullIntervalId.value = window.setInterval(pullExecStatus, 10000);
});
onUnmounted(() => {

View File

@@ -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