Merge branch 'dev' into hotfix
This commit is contained in:
@@ -31,14 +31,119 @@ package org.dromara.visor.common.constant;
|
||||
*/
|
||||
public interface ConfigKeys {
|
||||
|
||||
/**
|
||||
* SFTP 文件预览大小
|
||||
*/
|
||||
String SFTP_PREVIEW_SIZE = "sftp_previewSize";
|
||||
|
||||
/**
|
||||
* SFTP 重复文件备份
|
||||
*/
|
||||
String SFTP_UPLOAD_PRESENT_BACKUP = "sftp_uploadPresentBackup";
|
||||
|
||||
/**
|
||||
* SFTP 备份文件名称
|
||||
*/
|
||||
String SFTP_UPLOAD_BACKUP_FILE_NAME = "sftp_uploadBackupFileName";
|
||||
|
||||
/**
|
||||
* 加密公钥
|
||||
*/
|
||||
String ENCRYPT_PUBLIC_KEY = "encrypt.publicKey";
|
||||
String ENCRYPT_PUBLIC_KEY = "encrypt_publicKey";
|
||||
|
||||
/**
|
||||
* 加密私钥
|
||||
*/
|
||||
String ENCRYPT_PRIVATE_KEY = "encrypt.privateKey";
|
||||
String ENCRYPT_PRIVATE_KEY = "encrypt_privateKey";
|
||||
|
||||
/**
|
||||
* 日志前端显示行数
|
||||
*/
|
||||
String LOG_WEB_SCROLL_LINES = "log_webScrollLines";
|
||||
|
||||
/**
|
||||
* 日志加载偏移行
|
||||
*/
|
||||
String LOG_TRACKER_LOAD_LINES = "log_trackerLoadLines";
|
||||
|
||||
/**
|
||||
* 日志加载间隔毫秒
|
||||
*/
|
||||
String LOG_TRACKER_LOAD_INTERVAL = "log_trackerLoadInterval";
|
||||
|
||||
/**
|
||||
* 是否生成详细的执行日志
|
||||
*/
|
||||
String LOG_EXEC_DETAIL_LOG = "log_execDetailLog";
|
||||
|
||||
/**
|
||||
* 凭证有效期分
|
||||
*/
|
||||
String LOGIN_LOGIN_SESSION_TIME = "login_loginSessionTime";
|
||||
|
||||
/**
|
||||
* 允许多端登录
|
||||
*/
|
||||
String LOGIN_ALLOW_MULTI_DEVICE = "login_allowMultiDevice";
|
||||
|
||||
/**
|
||||
* 允许凭证续签
|
||||
*/
|
||||
String LOGIN_ALLOW_REFRESH = "login_allowRefresh";
|
||||
|
||||
/**
|
||||
* 凭证续签最大次数
|
||||
*/
|
||||
String LOGIN_MAX_REFRESH_COUNT = "login_maxRefreshCount";
|
||||
|
||||
/**
|
||||
* 凭证续签间隔分
|
||||
*/
|
||||
String LOGIN_REFRESH_INTERVAL = "login_refreshInterval";
|
||||
|
||||
/**
|
||||
* 登录失败锁定
|
||||
*/
|
||||
String LOGIN_LOGIN_FAILED_LOCK = "login_loginFailedLock";
|
||||
|
||||
/**
|
||||
* 登录失败锁定阈值分
|
||||
*/
|
||||
String LOGIN_LOGIN_FAILED_LOCK_THRESHOLD = "login_loginFailedLockThreshold";
|
||||
|
||||
/**
|
||||
* 登录失败锁定时间分
|
||||
*/
|
||||
String LOGIN_LOGIN_FAILED_LOCK_TIME = "login_loginFailedLockTime";
|
||||
|
||||
/**
|
||||
* 登录失败发信
|
||||
*/
|
||||
String LOGIN_LOGIN_FAILED_SEND = "login_loginFailedSend";
|
||||
|
||||
/**
|
||||
* 登录失败发信阈值
|
||||
*/
|
||||
String LOGIN_LOGIN_FAILED_SEND_THRESHOLD = "login_loginFailedSendThreshold";
|
||||
|
||||
/**
|
||||
* 是否开启自动清理命令记录
|
||||
*/
|
||||
String AUTO_CLEAR_EXEC_LOG_ENABLED = "autoClear_execLogEnabled";
|
||||
|
||||
/**
|
||||
* 自动清理命令记录保留天数
|
||||
*/
|
||||
String AUTO_CLEAR_EXEC_LOG_KEEP_DAYS = "autoClear_execLogKeepDays";
|
||||
|
||||
/**
|
||||
* 是否开启自动清理终端连接记录
|
||||
*/
|
||||
String AUTO_CLEAR_TERMINAL_LOG_ENABLED = "autoClear_terminalLogEnabled";
|
||||
|
||||
/**
|
||||
* 自动清理终端连接记录保留天数
|
||||
*/
|
||||
String AUTO_CLEAR_TERMINAL_LOG_KEEP_DAYS = "autoClear_terminalLogKeepDays";
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.asset.define.config;
|
||||
|
||||
import org.dromara.visor.common.config.ConfigRef;
|
||||
import org.dromara.visor.common.config.ConfigStore;
|
||||
import org.dromara.visor.common.constant.ConfigKeys;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 自动清理设置
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/6/24 15:01
|
||||
*/
|
||||
@Component
|
||||
public class AppAutoClearConfig {
|
||||
|
||||
/**
|
||||
* 是否开启自动清理命令记录
|
||||
*/
|
||||
private final ConfigRef<Boolean> execLogEnabled;
|
||||
|
||||
/**
|
||||
* 自动清理命令记录保留天数
|
||||
*/
|
||||
private final ConfigRef<Integer> execLogKeepDays;
|
||||
|
||||
/**
|
||||
* 是否开启自动清理终端连接记录
|
||||
*/
|
||||
private final ConfigRef<Boolean> terminalLogEnabled;
|
||||
|
||||
/**
|
||||
* 自动清理终端连接记录保留天数
|
||||
*/
|
||||
private final ConfigRef<Integer> terminalLogKeepDays;
|
||||
|
||||
public AppAutoClearConfig(ConfigStore configStore) {
|
||||
this.execLogEnabled = configStore.bool(ConfigKeys.AUTO_CLEAR_EXEC_LOG_ENABLED);
|
||||
this.execLogKeepDays = configStore.int32(ConfigKeys.AUTO_CLEAR_EXEC_LOG_KEEP_DAYS);
|
||||
this.terminalLogEnabled = configStore.bool(ConfigKeys.AUTO_CLEAR_TERMINAL_LOG_ENABLED);
|
||||
this.terminalLogKeepDays = configStore.int32(ConfigKeys.AUTO_CLEAR_TERMINAL_LOG_KEEP_DAYS);
|
||||
}
|
||||
|
||||
public Boolean getExecLogEnabled() {
|
||||
return execLogEnabled.value;
|
||||
}
|
||||
|
||||
public Integer getExecLogKeepDays() {
|
||||
return execLogKeepDays.value;
|
||||
}
|
||||
|
||||
public Boolean getTerminalLogEnabled() {
|
||||
return terminalLogEnabled.value;
|
||||
}
|
||||
|
||||
public Integer getTerminalLogKeepDays() {
|
||||
return terminalLogKeepDays.value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +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.asset.define.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.visor.common.entity.AutoClearConfig;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 批量执行日志自动清理配置
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/6/24 15:01
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ConfigurationProperties(prefix = "app.auto-clear.exec-log")
|
||||
public class AppExecLogAutoClearConfig extends AutoClearConfig {
|
||||
|
||||
}
|
||||
@@ -1,50 +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.asset.define.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 应用执行日志配置
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/4/25 13:36
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "app.exec-log")
|
||||
public class AppExecLogConfig {
|
||||
|
||||
/**
|
||||
* 是否拼接 ansi 执行状态日志
|
||||
*/
|
||||
private Boolean appendAnsi;
|
||||
|
||||
public AppExecLogConfig() {
|
||||
this.appendAnsi = true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.asset.define.config;
|
||||
|
||||
import org.dromara.visor.common.config.ConfigRef;
|
||||
import org.dromara.visor.common.config.ConfigStore;
|
||||
import org.dromara.visor.common.constant.ConfigKeys;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 应用日志设置
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/4/15 22:00
|
||||
*/
|
||||
@Component
|
||||
public class AppLogConfig {
|
||||
|
||||
/**
|
||||
* 日志加载偏移行
|
||||
*/
|
||||
private final ConfigRef<Integer> trackerLoadLines;
|
||||
|
||||
/**
|
||||
* 日志加载间隔毫秒
|
||||
*/
|
||||
private final ConfigRef<Integer> trackerLoadInterval;
|
||||
|
||||
/**
|
||||
* 是否生成详细的执行日志
|
||||
*/
|
||||
private final ConfigRef<Boolean> execDetailLog;
|
||||
|
||||
public AppLogConfig(ConfigStore configStore) {
|
||||
this.trackerLoadLines = configStore.int32(ConfigKeys.LOG_TRACKER_LOAD_LINES);
|
||||
this.trackerLoadInterval = configStore.int32(ConfigKeys.LOG_TRACKER_LOAD_INTERVAL);
|
||||
this.execDetailLog = configStore.bool(ConfigKeys.LOG_EXEC_DETAIL_LOG);
|
||||
}
|
||||
|
||||
public Integer getTrackerLoadLines() {
|
||||
return trackerLoadLines.value;
|
||||
}
|
||||
|
||||
public Integer getTrackerLoadInterval() {
|
||||
return trackerLoadInterval.value;
|
||||
}
|
||||
|
||||
public Boolean getExecDetailLog() {
|
||||
return execDetailLog.value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,8 +22,9 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.define.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.dromara.visor.common.config.ConfigRef;
|
||||
import org.dromara.visor.common.config.ConfigStore;
|
||||
import org.dromara.visor.common.constant.ConfigKeys;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -33,24 +34,40 @@ import org.springframework.stereotype.Component;
|
||||
* @version 1.0.0
|
||||
* @since 2024/4/15 22:00
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "app.sftp")
|
||||
public class AppSftpConfig {
|
||||
|
||||
/**
|
||||
* 上传文件时 文件存在是否备份
|
||||
* 文件预览大小
|
||||
*/
|
||||
private Boolean uploadPresentBackup;
|
||||
private final ConfigRef<Integer> previewSize;
|
||||
|
||||
/**
|
||||
* 重复文件备份
|
||||
*/
|
||||
private final ConfigRef<Boolean> uploadPresentBackup;
|
||||
|
||||
/**
|
||||
* 备份文件名称
|
||||
*/
|
||||
private String backupFileName;
|
||||
private final ConfigRef<String> uploadBackupFileName;
|
||||
|
||||
public AppSftpConfig() {
|
||||
this.uploadPresentBackup = true;
|
||||
this.backupFileName = "bk_${fileName}_${timestamp}";
|
||||
public AppSftpConfig(ConfigStore configStore) {
|
||||
this.previewSize = configStore.int32(ConfigKeys.SFTP_PREVIEW_SIZE);
|
||||
this.uploadPresentBackup = configStore.bool(ConfigKeys.SFTP_UPLOAD_PRESENT_BACKUP);
|
||||
this.uploadBackupFileName = configStore.string(ConfigKeys.SFTP_UPLOAD_BACKUP_FILE_NAME);
|
||||
}
|
||||
|
||||
public Integer getPreviewSize() {
|
||||
return previewSize.value;
|
||||
}
|
||||
|
||||
public Boolean getUploadPresentBackup() {
|
||||
return uploadPresentBackup.value;
|
||||
}
|
||||
|
||||
public String getUploadBackupFileName() {
|
||||
return uploadBackupFileName.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,44 +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.asset.define.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.visor.common.entity.AutoClearConfig;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 终端连接日志自动清理配置
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/6/24 15:01
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ConfigurationProperties(prefix = "app.auto-clear.terminal-connect-log")
|
||||
public class AppTerminalConnectLogAutoClearConfig extends AutoClearConfig {
|
||||
|
||||
}
|
||||
@@ -1,61 +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.asset.define.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 应用 tracker 配置
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/4/15 22:00
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "app.tracker")
|
||||
public class AppTrackerConfig {
|
||||
|
||||
/**
|
||||
* 加载偏移量 (行)
|
||||
*/
|
||||
private Integer offset;
|
||||
|
||||
/**
|
||||
* 延迟时间 (ms)
|
||||
*/
|
||||
private Integer delay;
|
||||
|
||||
/**
|
||||
* 文件未找到等待次数
|
||||
*/
|
||||
private Integer waitTimes;
|
||||
|
||||
public AppTrackerConfig() {
|
||||
this.offset = 300;
|
||||
this.delay = 100;
|
||||
this.waitTimes = 100;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,8 @@ package org.dromara.visor.module.asset.handler.host.exec.command;
|
||||
|
||||
import org.dromara.visor.module.asset.define.AssetThreadPools;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.handler.ExecTaskHandler;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量执行命令执行器
|
||||
@@ -38,10 +39,11 @@ public class ExecTaskExecutors {
|
||||
/**
|
||||
* 执行命令
|
||||
*
|
||||
* @param command command
|
||||
* @param id id
|
||||
* @param execHostIdList execHostIdList
|
||||
*/
|
||||
public static void start(ExecCommandDTO command) {
|
||||
AssetThreadPools.EXEC_TASK.execute(new ExecTaskHandler(command));
|
||||
public static void start(Long id, List<Long> execHostIdList) {
|
||||
AssetThreadPools.EXEC_TASK.execute(new ExecTaskHandler(id, execHostIdList));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,12 +25,13 @@ package org.dromara.visor.module.asset.handler.host.exec.command.handler;
|
||||
import cn.orionsec.kit.lang.exception.AuthenticationException;
|
||||
import cn.orionsec.kit.lang.exception.ConnectionRuntimeException;
|
||||
import cn.orionsec.kit.lang.exception.SftpException;
|
||||
import cn.orionsec.kit.lang.id.UUIds;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutChecker;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutEndpoint;
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.Exceptions;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.ansi.AnsiAppender;
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import cn.orionsec.kit.net.host.SessionStore;
|
||||
import cn.orionsec.kit.net.host.sftp.SftpExecutor;
|
||||
@@ -40,22 +41,29 @@ import com.alibaba.fastjson.JSON;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.constant.FileConst;
|
||||
import org.dromara.visor.common.enums.BooleanBit;
|
||||
import org.dromara.visor.common.enums.EndpointDefine;
|
||||
import org.dromara.visor.common.interfaces.FileClient;
|
||||
import org.dromara.visor.common.utils.PathUtils;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.module.asset.dao.ExecHostLogDAO;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecHostLogDO;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
|
||||
import org.dromara.visor.module.asset.entity.dto.TerminalConnectDTO;
|
||||
import org.dromara.visor.module.asset.enums.ExecHostStatusEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
|
||||
import org.dromara.visor.module.asset.enums.HostOsTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.log.manager.ExecLogManager;
|
||||
import org.dromara.visor.module.asset.handler.host.jsch.SessionStores;
|
||||
import org.dromara.visor.module.asset.service.TerminalService;
|
||||
import org.dromara.visor.module.asset.utils.ExecUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 命令执行器 基类
|
||||
@@ -75,16 +83,21 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
|
||||
private static final ExecHostLogDAO execHostLogDAO = SpringHolder.getBean(ExecHostLogDAO.class);
|
||||
|
||||
protected final ExecCommandDTO execCommand;
|
||||
@Getter
|
||||
protected final Long id;
|
||||
|
||||
protected final ExecCommandHostDTO execHostCommand;
|
||||
protected final Map<String, Object> builtParams;
|
||||
|
||||
private final TimeoutChecker<TimeoutEndpoint> timeoutChecker;
|
||||
protected final TimeoutChecker<TimeoutEndpoint> timeoutChecker;
|
||||
|
||||
protected final ExecLogDO execLog;
|
||||
|
||||
protected ExecHostLogDO execHostLog;
|
||||
|
||||
@Getter
|
||||
protected ExecHostStatusEnum status;
|
||||
|
||||
protected ExecHostLogDO updateRecord;
|
||||
private TerminalConnectDTO connect;
|
||||
|
||||
private OutputStream logOutputStream;
|
||||
|
||||
@@ -99,24 +112,28 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
|
||||
private volatile boolean interrupted;
|
||||
|
||||
public BaseExecCommandHandler(ExecCommandDTO execCommand,
|
||||
ExecCommandHostDTO execHostCommand,
|
||||
public BaseExecCommandHandler(Long id,
|
||||
ExecLogDO execLog,
|
||||
Map<String, Object> builtParams,
|
||||
TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
|
||||
this.status = ExecHostStatusEnum.WAITING;
|
||||
this.execCommand = execCommand;
|
||||
this.execHostCommand = execHostCommand;
|
||||
this.id = id;
|
||||
this.execLog = execLog;
|
||||
this.builtParams = builtParams;
|
||||
this.timeoutChecker = timeoutChecker;
|
||||
this.updateRecord = new ExecHostLogDO();
|
||||
this.status = ExecHostStatusEnum.WAITING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Long id = execHostCommand.getHostLogId();
|
||||
Exception ex = null;
|
||||
log.info("ExecCommandHandler run start id: {}, info: {}", id, JSON.toJSONString(execHostCommand));
|
||||
// 更新状态
|
||||
this.updateStatus(ExecHostStatusEnum.RUNNING, null);
|
||||
log.info("ExecCommandHandler run start id: {}", id);
|
||||
// 初始化数据以及修改状态
|
||||
if (!this.initData()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 初始化日志
|
||||
this.initLogOutputStream();
|
||||
// 执行命令
|
||||
this.execCommand();
|
||||
log.info("ExecCommandHandler run complete id: {}", id);
|
||||
@@ -135,13 +152,47 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化日志输出流
|
||||
* 初始化数据
|
||||
*
|
||||
* @throws Exception Exception
|
||||
* @return pass
|
||||
*/
|
||||
protected void initLogOutputStream() throws Exception {
|
||||
// 打开日志流
|
||||
this.logOutputStream = fileClient.getContentOutputStream(execHostCommand.getLogPath());
|
||||
private boolean initData() {
|
||||
Exception ex = null;
|
||||
try {
|
||||
// 查询任务
|
||||
this.execHostLog = execHostLogDAO.selectById(id);
|
||||
Valid.notNull(this.execHostLog, ErrorMessage.TASK_ABSENT);
|
||||
// 检查任务状态
|
||||
this.status = ExecHostStatusEnum.of(execHostLog.getStatus());
|
||||
Valid.eq(this.status, ExecHostStatusEnum.WAITING, ErrorMessage.TASK_ABSENT, ErrorMessage.ILLEGAL_STATUS);
|
||||
// 获取主机会话
|
||||
this.connect = terminalService.getTerminalConnectInfo(execHostLog.getHostId(), execLog.getUserId());
|
||||
// 获取内置参数
|
||||
Map<String, Object> commandParams = this.getCommandParams();
|
||||
// 获取实际命令
|
||||
String command = ExecUtils.format(execLog.getCommand(), commandParams);
|
||||
// 获取日志路径
|
||||
String logPath = fileClient.getReturnPath(EndpointDefine.EXEC_LOG.format(execHostLog.getLogId(), execHostLog.getHostId()));
|
||||
// 获取脚本路径
|
||||
String scriptPath = null;
|
||||
if (BooleanBit.toBoolean(execLog.getScriptExec())) {
|
||||
scriptPath = this.buildScriptPath();
|
||||
}
|
||||
execHostLog.setCommand(command);
|
||||
execHostLog.setParameter(JSON.toJSONString(commandParams));
|
||||
execHostLog.setLogPath(logPath);
|
||||
execHostLog.setScriptPath(scriptPath);
|
||||
} catch (Exception e) {
|
||||
log.error("BaseExecCommandHandler.initData error id: {}", id, e);
|
||||
ex = e;
|
||||
}
|
||||
boolean passed = ex == null;
|
||||
// 更新状态
|
||||
this.updateStatus(passed ? ExecHostStatusEnum.RUNNING : ExecHostStatusEnum.FAILED, ex, (s) -> {
|
||||
// 修改其他参数
|
||||
s.setCommand(execHostLog.getCommand());
|
||||
});
|
||||
return passed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,29 +201,36 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
protected void execCommand() throws Exception {
|
||||
// 初始化日志
|
||||
this.initLogOutputStream();
|
||||
// 打开会话
|
||||
TerminalConnectDTO connect = terminalService.getTerminalConnectInfo(execHostCommand.getHostId(), execCommand.getUserId());
|
||||
this.sessionStore = SessionStores.openSessionStore(connect);
|
||||
if (Booleans.isTrue(execCommand.getScriptExec())) {
|
||||
if (BooleanBit.toBoolean(execLog.getScriptExec())) {
|
||||
// 上传脚本文件
|
||||
this.uploadScriptFile();
|
||||
// 执行脚本文件
|
||||
this.executor = sessionStore.getCommandExecutor(execHostCommand.getScriptPath());
|
||||
this.executor = sessionStore.getCommandExecutor(execHostLog.getScriptPath());
|
||||
} else {
|
||||
// 执行命令
|
||||
byte[] command = Strings.replaceCRLF(execHostCommand.getCommand()).getBytes(execHostCommand.getCharset());
|
||||
byte[] command = execHostLog.getCommand().getBytes(connect.getCharset());
|
||||
this.executor = sessionStore.getCommandExecutor(command);
|
||||
}
|
||||
// 执行命令
|
||||
executor.timeout(execCommand.getTimeout(), TimeUnit.SECONDS, timeoutChecker);
|
||||
executor.timeout(execLog.getTimeout(), TimeUnit.SECONDS, timeoutChecker);
|
||||
executor.merge();
|
||||
executor.transfer(logOutputStream);
|
||||
executor.connect();
|
||||
executor.exec();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化日志输出流
|
||||
*
|
||||
* @throws Exception Exception
|
||||
*/
|
||||
protected void initLogOutputStream() throws Exception {
|
||||
// 打开日志流
|
||||
this.logOutputStream = fileClient.getContentOutputStream(execHostLog.getLogPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传脚本文件
|
||||
*/
|
||||
@@ -180,14 +238,14 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
SftpExecutor sftpExecutor = null;
|
||||
try {
|
||||
// 打开 sftp
|
||||
sftpExecutor = sessionStore.getSftpExecutor(execHostCommand.getFileNameCharset());
|
||||
sftpExecutor = sessionStore.getSftpExecutor(connect.getFileNameCharset());
|
||||
sftpExecutor.connect();
|
||||
// 文件上传必须要以 / 开头
|
||||
String scriptPath = PathUtils.prependSeparator(execHostCommand.getScriptPath());
|
||||
String scriptPath = PathUtils.prependSeparator(execHostLog.getScriptPath());
|
||||
// 创建文件
|
||||
sftpExecutor.touch(scriptPath);
|
||||
// 写入命令
|
||||
byte[] command = Strings.replaceCRLF(execHostCommand.getCommand()).getBytes(execHostCommand.getFileContentCharset());
|
||||
byte[] command = execHostLog.getCommand().getBytes(connect.getFileContentCharset());
|
||||
sftpExecutor.write(scriptPath, command);
|
||||
// 修改权限
|
||||
sftpExecutor.changeMode(scriptPath, 777);
|
||||
@@ -207,16 +265,16 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
// 执行回调
|
||||
if (this.interrupted) {
|
||||
// 中断执行
|
||||
this.updateStatus(ExecHostStatusEnum.INTERRUPTED, null);
|
||||
this.updateStatus(ExecHostStatusEnum.INTERRUPTED, null, null);
|
||||
} else if (e != null) {
|
||||
// 执行失败
|
||||
this.updateStatus(ExecHostStatusEnum.FAILED, e);
|
||||
this.updateStatus(ExecHostStatusEnum.FAILED, e, null);
|
||||
} else if (executor.isTimeout()) {
|
||||
// 更新执行超时
|
||||
this.updateStatus(ExecHostStatusEnum.TIMEOUT, null);
|
||||
this.updateStatus(ExecHostStatusEnum.TIMEOUT, null, null);
|
||||
} else {
|
||||
// 更新执行完成
|
||||
this.updateStatus(ExecHostStatusEnum.COMPLETED, null);
|
||||
this.updateStatus(ExecHostStatusEnum.COMPLETED, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,33 +297,45 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
*
|
||||
* @param status status
|
||||
* @param ex ex
|
||||
* @param filler filler
|
||||
*/
|
||||
private void updateStatus(ExecHostStatusEnum status, Exception ex) {
|
||||
private void updateStatus(ExecHostStatusEnum status, Exception ex, Consumer<ExecHostLogDO> filler) {
|
||||
this.status = status;
|
||||
Long id = execHostCommand.getHostLogId();
|
||||
String statusName = status.name();
|
||||
execHostLog.setStatus(statusName);
|
||||
log.info("BaseExecCommandHandler.updateStatus start id: {}, status: {}", id, statusName);
|
||||
try {
|
||||
updateRecord.setId(id);
|
||||
updateRecord.setStatus(statusName);
|
||||
if (ExecHostStatusEnum.RUNNING.equals(status)) {
|
||||
// 运行中
|
||||
updateRecord.setStartTime(new Date());
|
||||
execHostLog.setStartTime(new Date());
|
||||
} else if (ExecHostStatusEnum.COMPLETED.equals(status)) {
|
||||
// 完成
|
||||
updateRecord.setFinishTime(new Date());
|
||||
updateRecord.setExitCode(executor.getExitCode());
|
||||
execHostLog.setFinishTime(new Date());
|
||||
execHostLog.setExitCode(executor.getExitCode());
|
||||
this.exitCode = executor.getExitCode();
|
||||
} else if (ExecHostStatusEnum.FAILED.equals(status)) {
|
||||
// 失败
|
||||
updateRecord.setFinishTime(new Date());
|
||||
updateRecord.setErrorMessage(this.getErrorMessage(ex));
|
||||
execHostLog.setFinishTime(new Date());
|
||||
execHostLog.setErrorMessage(this.getErrorMessage(ex));
|
||||
} else if (ExecHostStatusEnum.TIMEOUT.equals(status)) {
|
||||
// 超时
|
||||
updateRecord.setFinishTime(new Date());
|
||||
execHostLog.setFinishTime(new Date());
|
||||
} else if (ExecHostStatusEnum.INTERRUPTED.equals(status)) {
|
||||
// 中断
|
||||
updateRecord.setFinishTime(new Date());
|
||||
execHostLog.setFinishTime(new Date());
|
||||
}
|
||||
// 选择性更新
|
||||
ExecHostLogDO updateRecord = ExecHostLogDO.builder()
|
||||
.id(execHostLog.getId())
|
||||
.status(execHostLog.getStatus())
|
||||
.exitCode(execHostLog.getExitCode())
|
||||
.startTime(execHostLog.getStartTime())
|
||||
.finishTime(execHostLog.getFinishTime())
|
||||
.errorMessage(execHostLog.getErrorMessage())
|
||||
.build();
|
||||
// 填充参数
|
||||
if (filler != null) {
|
||||
filler.accept(updateRecord);
|
||||
}
|
||||
int effect = execHostLogDAO.updateById(updateRecord);
|
||||
log.info("BaseExecCommandHandler.updateStatus finish id: {}, effect: {}", id, effect);
|
||||
@@ -281,8 +351,7 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
|
||||
@Override
|
||||
public void interrupt() {
|
||||
log.info("BaseExecCommandHandler.interrupt id: {}, interrupted: {}, closed: {}",
|
||||
execHostCommand.getHostLogId(), interrupted, closed);
|
||||
log.info("BaseExecCommandHandler.interrupt id: {}, interrupted: {}, closed: {}", id, interrupted, closed);
|
||||
if (this.interrupted || this.closed) {
|
||||
return;
|
||||
}
|
||||
@@ -294,8 +363,7 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
log.info("BaseExecCommandHandler.closed id: {}, closed: {}",
|
||||
execHostCommand.getHostLogId(), closed);
|
||||
log.info("BaseExecCommandHandler.closed id: {}, closed: {}", id, closed);
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
@@ -303,7 +371,7 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
Streams.close(logOutputStream);
|
||||
Streams.close(executor);
|
||||
Streams.close(sessionStore);
|
||||
execLogManager.asyncCloseTailFile(execHostCommand.getLogPath());
|
||||
execLogManager.asyncCloseTailFile(execHostLog.getLogPath());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,9 +404,40 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
return Strings.retain(message, 250);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getHostId() {
|
||||
return execHostCommand.getHostId();
|
||||
/**
|
||||
* 获取命令实际参数
|
||||
*
|
||||
* @return params
|
||||
*/
|
||||
private Map<String, Object> getCommandParams() {
|
||||
String uuid = UUIds.random();
|
||||
Map<String, Object> params = Maps.newMap(builtParams);
|
||||
params.put("hostId", connect.getHostId());
|
||||
params.put("hostName", connect.getHostName());
|
||||
params.put("hostCode", connect.getHostCode());
|
||||
params.put("hostAddress", connect.getHostAddress());
|
||||
params.put("hostPort", connect.getHostPort());
|
||||
params.put("hostUsername", connect.getUsername());
|
||||
params.put("hostUuid", uuid);
|
||||
params.put("hostUuidShort", uuid.replace("-", Strings.EMPTY));
|
||||
params.put("osType", connect.getOsType());
|
||||
params.put("charset", connect.getCharset());
|
||||
params.put("scriptPath", execHostLog.getScriptPath());
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建脚本路径
|
||||
*
|
||||
* @return scriptPath
|
||||
*/
|
||||
private String buildScriptPath() {
|
||||
HostOsTypeEnum os = HostOsTypeEnum.of(connect.getOsType());
|
||||
String name = FileConst.EXEC
|
||||
+ "/" + execHostLog.getLogId()
|
||||
+ "/" + id
|
||||
+ os.getScriptSuffix();
|
||||
return PathUtils.buildAppPath(HostOsTypeEnum.WINDOWS.equals(os), connect.getUsername(), FileConst.SCRIPT, name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,15 +24,16 @@ package org.dromara.visor.module.asset.handler.host.exec.command.handler;
|
||||
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutChecker;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutEndpoint;
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.ansi.AnsiAppender;
|
||||
import cn.orionsec.kit.lang.utils.ansi.style.color.AnsiForeground;
|
||||
import cn.orionsec.kit.lang.utils.time.Dates;
|
||||
import cn.orionsec.kit.net.host.ssh.ExitCode;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.enums.BooleanBit;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
|
||||
import org.dromara.visor.module.asset.enums.ExecHostStatusEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 命令执行器 ansi 日志输出
|
||||
@@ -41,10 +42,13 @@ import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecComman
|
||||
* @version 1.0.0
|
||||
* @since 2024/4/25 18:19
|
||||
*/
|
||||
public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
|
||||
public class ExecCommandDetailHandler extends BaseExecCommandHandler {
|
||||
|
||||
public ExecCommandAnsiHandler(ExecCommandDTO execCommand, ExecCommandHostDTO execHostCommand, TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
|
||||
super(execCommand, execHostCommand, timeoutChecker);
|
||||
public ExecCommandDetailHandler(Long id,
|
||||
ExecLogDO execLog,
|
||||
Map<String, Object> builtParams,
|
||||
TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
|
||||
super(id, execLog, builtParams, timeoutChecker);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,52 +60,52 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
|
||||
.append(this.getCurrentTime())
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "执行记录: ")
|
||||
.append(execCommand.getLogId())
|
||||
.append(execLog.getId())
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "执行描述: ")
|
||||
.append(execCommand.getDescription())
|
||||
.append(execLog.getDescription())
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "执行用户: ")
|
||||
.append(execCommand.getUsername());
|
||||
.append(execLog.getUsername());
|
||||
// 非系统用户执行添加 userId
|
||||
if (Const.SYSTEM_USER_ID.equals(execCommand.getUserId())) {
|
||||
if (Const.SYSTEM_USER_ID.equals(execLog.getUserId())) {
|
||||
appender.newLine();
|
||||
} else {
|
||||
appender.append(" (")
|
||||
.append(execCommand.getUserId())
|
||||
.append(execLog.getUserId())
|
||||
.append(")")
|
||||
.newLine();
|
||||
}
|
||||
// 执行序列
|
||||
if (execCommand.getExecSeq() != null) {
|
||||
if (execLog.getExecSeq() != null) {
|
||||
appender.append(AnsiForeground.BRIGHT_BLUE, "执行序列: ")
|
||||
.append('#')
|
||||
.append(execCommand.getExecSeq())
|
||||
.append(execLog.getExecSeq())
|
||||
.newLine();
|
||||
}
|
||||
appender.append(AnsiForeground.BRIGHT_BLUE, "执行主机: ")
|
||||
.append(execHostCommand.getHostName())
|
||||
.append(execHostLog.getHostName())
|
||||
.append(" (")
|
||||
.append(execHostCommand.getHostId())
|
||||
.append(execHostLog.getHostId())
|
||||
.append(")")
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "主机地址: ")
|
||||
.append(execHostCommand.getHostAddress())
|
||||
.append(execHostLog.getHostAddress())
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "超时时间: ")
|
||||
.append(execCommand.getTimeout())
|
||||
.append(execLog.getTimeout())
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "脚本执行: ")
|
||||
.append(execCommand.getScriptExec())
|
||||
.append(execLog.getScriptExec())
|
||||
.newLine()
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_GREEN, "> 执行命令 ")
|
||||
.newLine()
|
||||
.append(execHostCommand.getCommand())
|
||||
.append(execHostLog.getCommand())
|
||||
.newLine()
|
||||
.newLine();
|
||||
// 非脚本执行拼接开始执行日志
|
||||
if (!Booleans.isTrue(execCommand.getScriptExec())) {
|
||||
if (!BooleanBit.toBoolean(execLog.getScriptExec())) {
|
||||
appender.append(AnsiForeground.BRIGHT_GREEN, "> 开始执行命令 ")
|
||||
.append(this.getCurrentTime())
|
||||
.newLine();
|
||||
@@ -119,7 +123,7 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
|
||||
.append(this.getCurrentTime())
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "文件路径: ")
|
||||
.append(execHostCommand.getScriptPath())
|
||||
.append(execHostLog.getScriptPath())
|
||||
.newLine();
|
||||
this.appendLog(startAppender);
|
||||
// 上传脚本文件
|
||||
@@ -170,9 +174,9 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
|
||||
appender.append(AnsiForeground.BRIGHT_YELLOW, "< 命令执行超时 ")
|
||||
.append(this.getCurrentTime())
|
||||
.newLine();
|
||||
} else {
|
||||
long ms = updateRecord.getFinishTime().getTime() - updateRecord.getStartTime().getTime();
|
||||
Integer exitCode = updateRecord.getExitCode();
|
||||
} else if (this.status == ExecHostStatusEnum.COMPLETED) {
|
||||
long ms = execHostLog.getFinishTime().getTime() - execHostLog.getStartTime().getTime();
|
||||
Integer exitCode = execHostLog.getExitCode();
|
||||
// 执行完成
|
||||
appender.append(AnsiForeground.BRIGHT_GREEN, "< 命令执行完成 ")
|
||||
.append(this.getCurrentTime())
|
||||
@@ -25,8 +25,9 @@ package org.dromara.visor.module.asset.handler.host.exec.command.handler;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutChecker;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutEndpoint;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 命令执行器 原始日志输出
|
||||
@@ -38,8 +39,11 @@ import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecComman
|
||||
@Slf4j
|
||||
public class ExecCommandOriginHandler extends BaseExecCommandHandler {
|
||||
|
||||
public ExecCommandOriginHandler(ExecCommandDTO execCommand, ExecCommandHostDTO execHostCommand, TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
|
||||
super(execCommand, execHostCommand, timeoutChecker);
|
||||
public ExecCommandOriginHandler(Long id,
|
||||
ExecLogDO execLog,
|
||||
Map<String, Object> builtParams,
|
||||
TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
|
||||
super(id, execLog, builtParams, timeoutChecker);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,11 +22,14 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.handler.host.exec.command.handler;
|
||||
|
||||
import cn.orionsec.kit.lang.id.UUIds;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutChecker;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutCheckers;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutEndpoint;
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
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.lang.utils.time.Dates;
|
||||
@@ -34,17 +37,17 @@ import cn.orionsec.kit.net.host.ssh.ExitCode;
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.constant.ExtraFieldConst;
|
||||
import org.dromara.visor.module.asset.dao.ExecLogDAO;
|
||||
import org.dromara.visor.module.asset.define.AssetThreadPools;
|
||||
import org.dromara.visor.module.asset.define.config.AppExecLogConfig;
|
||||
import org.dromara.visor.module.asset.define.config.AppLogConfig;
|
||||
import org.dromara.visor.module.asset.define.message.ExecMessageDefine;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
|
||||
import org.dromara.visor.module.asset.enums.ExecHostStatusEnum;
|
||||
import org.dromara.visor.module.asset.enums.ExecStatusEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.manager.ExecTaskManager;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
|
||||
import org.dromara.visor.module.asset.utils.ExecUtils;
|
||||
import org.dromara.visor.module.infra.api.SystemMessageApi;
|
||||
import org.dromara.visor.module.infra.entity.dto.message.SystemMessageDTO;
|
||||
|
||||
@@ -63,15 +66,21 @@ import java.util.Map;
|
||||
@Slf4j
|
||||
public class ExecTaskHandler implements IExecTaskHandler {
|
||||
|
||||
private static final AppLogConfig appLogConfig = SpringHolder.getBean(AppLogConfig.class);
|
||||
|
||||
private static final ExecLogDAO execLogDAO = SpringHolder.getBean(ExecLogDAO.class);
|
||||
|
||||
private static final ExecTaskManager execTaskManager = SpringHolder.getBean(ExecTaskManager.class);
|
||||
|
||||
private static final AppExecLogConfig appExecLogConfig = SpringHolder.getBean(AppExecLogConfig.class);
|
||||
|
||||
private static final SystemMessageApi systemMessageApi = SpringHolder.getBean(SystemMessageApi.class);
|
||||
|
||||
private final ExecCommandDTO execCommand;
|
||||
private final Long id;
|
||||
|
||||
private final List<Long> execHostIdList;
|
||||
|
||||
private ExecLogDO execLog;
|
||||
|
||||
private Map<String, Object> builtParams;
|
||||
|
||||
private TimeoutChecker<TimeoutEndpoint> timeoutChecker;
|
||||
|
||||
@@ -80,14 +89,19 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
||||
|
||||
private Date startTime;
|
||||
|
||||
public ExecTaskHandler(ExecCommandDTO execCommand) {
|
||||
this.execCommand = execCommand;
|
||||
public ExecTaskHandler(Long id, List<Long> execHostIdList) {
|
||||
this.id = id;
|
||||
this.execHostIdList = execHostIdList;
|
||||
this.handlers = Lists.newList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Long id = execCommand.getLogId();
|
||||
log.info("ExecTaskHandler start id: {}", id);
|
||||
// 初始化数据
|
||||
if (!this.initData()) {
|
||||
return;
|
||||
}
|
||||
// 添加任务
|
||||
execTaskManager.addTask(id, this);
|
||||
log.info("ExecTaskHandler.run start id: {}", id);
|
||||
@@ -115,17 +129,37 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
||||
|
||||
@Override
|
||||
public void interrupt() {
|
||||
log.info("ExecTaskHandler-interrupt id: {}", execCommand.getLogId());
|
||||
log.info("ExecTaskHandler-interrupt id: {}", id);
|
||||
handlers.forEach(IExecCommandHandler::interrupt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
log.info("ExecTaskHandler-close id: {}", execCommand.getLogId());
|
||||
log.info("ExecTaskHandler-close id: {}", id);
|
||||
Streams.close(timeoutChecker);
|
||||
this.handlers.forEach(Streams::close);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*
|
||||
* @return pass
|
||||
*/
|
||||
private boolean initData() {
|
||||
try {
|
||||
// 查询任务
|
||||
this.execLog = execLogDAO.selectById(id);
|
||||
Valid.notNull(execLog, ErrorMessage.TASK_ABSENT);
|
||||
Valid.eq(execLog.getStatus(), ExecStatusEnum.WAITING.name(), ErrorMessage.ILLEGAL_STATUS);
|
||||
// 获取内置变量
|
||||
this.builtParams = this.getBaseBuiltinParams();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("ExecTaskHandler.init error id: {}", id, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行主机命令
|
||||
*
|
||||
@@ -133,19 +167,18 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
||||
*/
|
||||
private void runHostCommand() throws Exception {
|
||||
// 超时检查
|
||||
if (execCommand.getTimeout() != 0) {
|
||||
if (execLog.getTimeout() != 0) {
|
||||
this.timeoutChecker = TimeoutCheckers.create();
|
||||
AssetThreadPools.TIMEOUT_CHECK.execute(this.timeoutChecker);
|
||||
}
|
||||
// 执行命令
|
||||
List<ExecCommandHostDTO> hosts = execCommand.getHosts();
|
||||
if (hosts.size() == 1) {
|
||||
if (execHostIdList.size() == 1) {
|
||||
// 单个主机直接执行
|
||||
IExecCommandHandler handler = this.createCommandHandler(hosts.get(0));
|
||||
IExecCommandHandler handler = this.createCommandHandler(execHostIdList.get(0));
|
||||
handlers.add(handler);
|
||||
handler.run();
|
||||
} else {
|
||||
hosts.stream()
|
||||
execHostIdList.stream()
|
||||
.map(this::createCommandHandler)
|
||||
.forEach(handlers::add);
|
||||
// 多个主机异步阻塞执行
|
||||
@@ -156,16 +189,16 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
||||
/**
|
||||
* 创建命令执行器
|
||||
*
|
||||
* @param host host
|
||||
* @param execHostId execHostId
|
||||
* @return handler
|
||||
*/
|
||||
private IExecCommandHandler createCommandHandler(ExecCommandHostDTO host) {
|
||||
if (Booleans.isTrue(appExecLogConfig.getAppendAnsi())) {
|
||||
// ansi 日志
|
||||
return new ExecCommandAnsiHandler(execCommand, host, timeoutChecker);
|
||||
private IExecCommandHandler createCommandHandler(Long execHostId) {
|
||||
if (Booleans.isTrue(appLogConfig.getExecAppendAnsi())) {
|
||||
// 详细日志
|
||||
return new ExecCommandDetailHandler(id, execLog, builtParams, timeoutChecker);
|
||||
} else {
|
||||
// 原始日志
|
||||
return new ExecCommandOriginHandler(execCommand, host, timeoutChecker);
|
||||
return new ExecCommandOriginHandler(id, execLog, builtParams, timeoutChecker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +208,6 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
||||
* @param status status
|
||||
*/
|
||||
private void updateStatus(ExecStatusEnum status) {
|
||||
Long id = execCommand.getLogId();
|
||||
try {
|
||||
String statusName = status.name();
|
||||
log.info("ExecTaskHandler-updateStatus start id: {}, status: {}", id, statusName);
|
||||
@@ -214,16 +246,43 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
||||
}
|
||||
// 参数
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put(ExtraFieldConst.ID, execCommand.getLogId());
|
||||
params.put(ExtraFieldConst.ID, id);
|
||||
params.put(ExtraFieldConst.TIME, Dates.format(this.startTime, Dates.MD_HM));
|
||||
SystemMessageDTO message = SystemMessageDTO.builder()
|
||||
.receiverId(execCommand.getUserId())
|
||||
.receiverUsername(execCommand.getUsername())
|
||||
.relKey(String.valueOf(execCommand.getLogId()))
|
||||
.receiverId(execLog.getUserId())
|
||||
.receiverUsername(execLog.getUsername())
|
||||
.relKey(String.valueOf(id))
|
||||
.params(params)
|
||||
.build();
|
||||
// 发送
|
||||
systemMessageApi.create(ExecMessageDefine.EXEC_FAILED, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础内置参数
|
||||
*
|
||||
* @return params
|
||||
*/
|
||||
private Map<String, Object> getBaseBuiltinParams() {
|
||||
String uuid = UUIds.random();
|
||||
Date date = new Date();
|
||||
// 输入参数
|
||||
Map<String, Object> params = ExecUtils.extraSchemaParams(execLog.getParameterSchema());
|
||||
// 添加内置参数
|
||||
params.put("userId", execLog.getUserId());
|
||||
params.put("username", execLog.getUsername());
|
||||
params.put("source", execLog.getSource());
|
||||
params.put("sourceId", execLog.getSourceId());
|
||||
params.put("seq", execLog.getExecSeq());
|
||||
params.put("execId", id);
|
||||
params.put("scriptExec", execLog.getScriptExec());
|
||||
params.put("uuid", uuid);
|
||||
params.put("uuidShort", uuid.replace("-", Strings.EMPTY));
|
||||
params.put("timestampMillis", date.getTime());
|
||||
params.put("timestamp", date.getTime() / Dates.SECOND_STAMP);
|
||||
params.put("date", Dates.format(date, Dates.YMD));
|
||||
params.put("datetime", Dates.format(date, Dates.YMD_HMS));
|
||||
return params;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,10 +61,10 @@ public interface IExecCommandHandler extends Runnable, SafeCloseable {
|
||||
Integer getExitCode();
|
||||
|
||||
/**
|
||||
* 获取主机 id
|
||||
* 获取任务 id
|
||||
*
|
||||
* @return hostId
|
||||
*/
|
||||
Long getHostId();
|
||||
Long getId();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,85 +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.asset.handler.host.exec.command.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量执行启动对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/3/11 15:46
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ExecCommandDTO {
|
||||
|
||||
/**
|
||||
* logId
|
||||
*/
|
||||
private Long logId;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 执行描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 执行序列
|
||||
*/
|
||||
private Integer execSeq;
|
||||
|
||||
/**
|
||||
* 超时时间
|
||||
*/
|
||||
private Integer timeout;
|
||||
|
||||
/**
|
||||
* 是否使用脚本执行
|
||||
*/
|
||||
private Boolean scriptExec;
|
||||
|
||||
/**
|
||||
* 执行主机
|
||||
*/
|
||||
private List<ExecCommandHostDTO> hosts;
|
||||
|
||||
}
|
||||
@@ -1,98 +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.asset.handler.host.exec.command.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 批量执行启动主机对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/3/11 15:46
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ExecCommandHostDTO {
|
||||
|
||||
/**
|
||||
* hostLogId
|
||||
*/
|
||||
private Long hostLogId;
|
||||
|
||||
/**
|
||||
* hostId
|
||||
*/
|
||||
private Long hostId;
|
||||
|
||||
/**
|
||||
* 主机名称
|
||||
*/
|
||||
private String hostName;
|
||||
|
||||
/**
|
||||
* 主机地址
|
||||
*/
|
||||
private String hostAddress;
|
||||
|
||||
/**
|
||||
* 日志文件路径
|
||||
*/
|
||||
private String logPath;
|
||||
|
||||
/**
|
||||
* 脚本路径
|
||||
*/
|
||||
private String scriptPath;
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
*/
|
||||
private String command;
|
||||
|
||||
/**
|
||||
* 主机用户
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 命令编码
|
||||
*/
|
||||
private String charset;
|
||||
|
||||
/**
|
||||
* 文件名称编码
|
||||
*/
|
||||
private String fileNameCharset;
|
||||
|
||||
/**
|
||||
* 文件内容编码
|
||||
*/
|
||||
private String fileContentCharset;
|
||||
|
||||
}
|
||||
@@ -82,6 +82,9 @@ public class ExecLogManager {
|
||||
* @param path path
|
||||
*/
|
||||
public void asyncCloseTailFile(String path) {
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
Threads.start(() -> {
|
||||
try {
|
||||
// 获取当前路径的全部追踪器
|
||||
|
||||
@@ -29,8 +29,9 @@ import cn.orionsec.kit.ext.tail.mode.FileOffsetMode;
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.framework.websocket.core.utils.WebSockets;
|
||||
import org.dromara.visor.module.asset.define.config.AppTrackerConfig;
|
||||
import org.dromara.visor.module.asset.define.config.AppLogConfig;
|
||||
import org.dromara.visor.module.asset.entity.dto.ExecHostLogTailDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.log.constant.LogConst;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
@@ -45,7 +46,7 @@ import org.springframework.web.socket.WebSocketSession;
|
||||
@Slf4j
|
||||
public class ExecLogTracker implements IExecLogTracker {
|
||||
|
||||
private static final AppTrackerConfig trackerConfig = SpringHolder.getBean(AppTrackerConfig.class);
|
||||
private static final AppLogConfig appLogConfig = SpringHolder.getBean(AppLogConfig.class);
|
||||
|
||||
private final WebSocketSession session;
|
||||
|
||||
@@ -76,9 +77,9 @@ public class ExecLogTracker implements IExecLogTracker {
|
||||
try {
|
||||
this.tracker = new DelayTrackerListener(absolutePath, this);
|
||||
tracker.charset(config.getCharset());
|
||||
tracker.delayMillis(trackerConfig.getDelay());
|
||||
tracker.offset(FileOffsetMode.LINE, trackerConfig.getOffset());
|
||||
tracker.notFoundMode(FileNotFoundMode.WAIT_COUNT, trackerConfig.getWaitTimes());
|
||||
tracker.delayMillis(appLogConfig.getTrackerDelay());
|
||||
tracker.offset(FileOffsetMode.LINE, appLogConfig.getTrackerOffset());
|
||||
tracker.notFoundMode(FileNotFoundMode.WAIT_COUNT, Const.N_10);
|
||||
// 开始监听文件
|
||||
tracker.run();
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -36,21 +36,13 @@ import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
|
||||
public interface ExecCommandService {
|
||||
|
||||
/**
|
||||
* 批量执行命令
|
||||
* 执行命令
|
||||
*
|
||||
* @param request request
|
||||
* @return result
|
||||
*/
|
||||
ExecLogVO execCommand(ExecCommandRequest request);
|
||||
|
||||
/**
|
||||
* 批量执行命令
|
||||
*
|
||||
* @param request request
|
||||
* @return result
|
||||
*/
|
||||
ExecLogVO execCommandWithSource(ExecCommandExecDTO request);
|
||||
|
||||
/**
|
||||
* 重新执行命令
|
||||
*
|
||||
@@ -59,4 +51,20 @@ public interface ExecCommandService {
|
||||
*/
|
||||
ExecLogVO reExecCommand(Long logId);
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
*
|
||||
* @param request request
|
||||
* @return result
|
||||
*/
|
||||
ExecLogVO execCommandWithSource(ExecCommandExecDTO request);
|
||||
|
||||
/**
|
||||
* 创建执行命令
|
||||
*
|
||||
* @param request request
|
||||
* @return result
|
||||
*/
|
||||
ExecLogVO createCommandWithSource(ExecCommandExecDTO request);
|
||||
|
||||
}
|
||||
|
||||
@@ -22,26 +22,15 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.service.impl;
|
||||
|
||||
import cn.orionsec.kit.lang.annotation.Keep;
|
||||
import cn.orionsec.kit.lang.function.Functions;
|
||||
import cn.orionsec.kit.lang.id.UUIds;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.Valid;
|
||||
import cn.orionsec.kit.lang.utils.collect.Lists;
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import cn.orionsec.kit.lang.utils.json.matcher.NoMatchStrategy;
|
||||
import cn.orionsec.kit.lang.utils.json.matcher.ReplacementFormatter;
|
||||
import cn.orionsec.kit.lang.utils.json.matcher.ReplacementFormatters;
|
||||
import cn.orionsec.kit.lang.utils.time.Dates;
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
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.FileConst;
|
||||
import org.dromara.visor.common.enums.EndpointDefine;
|
||||
import org.dromara.visor.common.interfaces.FileClient;
|
||||
import org.dromara.visor.common.security.LoginUser;
|
||||
import org.dromara.visor.common.utils.PathUtils;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
|
||||
import org.dromara.visor.module.asset.convert.ExecConvert;
|
||||
@@ -54,25 +43,19 @@ import org.dromara.visor.module.asset.entity.domain.ExecHostLogDO;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostDO;
|
||||
import org.dromara.visor.module.asset.entity.dto.ExecCommandExecDTO;
|
||||
import org.dromara.visor.module.asset.entity.dto.ExecParameterSchemaDTO;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecCommandRequest;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecHostLogVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
|
||||
import org.dromara.visor.module.asset.enums.*;
|
||||
import org.dromara.visor.module.asset.handler.host.config.model.HostSshConfigModel;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.ExecTaskExecutors;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
|
||||
import org.dromara.visor.module.asset.service.AssetAuthorizedDataService;
|
||||
import org.dromara.visor.module.asset.service.ExecCommandService;
|
||||
import org.dromara.visor.module.asset.service.HostConfigService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -86,15 +69,8 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class ExecCommandServiceImpl implements ExecCommandService {
|
||||
|
||||
private static final ReplacementFormatter FORMATTER = ReplacementFormatters.create("@{{ ", " }}")
|
||||
.noMatchStrategy(NoMatchStrategy.KEEP);
|
||||
|
||||
private static final int DESC_OMIT = 60;
|
||||
|
||||
@Keep
|
||||
@Resource
|
||||
private FileClient logsFileClient;
|
||||
|
||||
@Resource
|
||||
private ExecLogDAO execLogDAO;
|
||||
|
||||
@@ -104,17 +80,12 @@ public class ExecCommandServiceImpl implements ExecCommandService {
|
||||
@Resource
|
||||
private HostDAO hostDAO;
|
||||
|
||||
@Resource
|
||||
private HostConfigService hostConfigService;
|
||||
|
||||
@Resource
|
||||
private AssetAuthorizedDataService assetAuthorizedDataService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ExecLogVO execCommand(ExecCommandRequest request) {
|
||||
log.info("ExecService.startExecCommand start params: {}", JSON.toJSONString(request));
|
||||
Valid.valid(ScriptExecEnum::of, request.getScriptExec());
|
||||
log.info("ExecService.execCommand start params: {}", JSON.toJSONString(request));
|
||||
LoginUser user = Valid.notNull(SecurityUtils.getLoginUser());
|
||||
Long userId = user.getId();
|
||||
List<Long> hostIdList = request.getHostIdList();
|
||||
@@ -123,26 +94,58 @@ public class ExecCommandServiceImpl implements ExecCommandService {
|
||||
hostIdList.removeIf(s -> !authorizedHostIdList.contains(s));
|
||||
log.info("ExecService.startExecCommand host hostList: {}", hostIdList);
|
||||
Valid.notEmpty(hostIdList, ErrorMessage.CHECK_AUTHORIZED_HOST);
|
||||
// 执行命令
|
||||
// 创建命令
|
||||
ExecCommandExecDTO execRequest = ExecConvert.MAPPER.to(request);
|
||||
execRequest.setUserId(userId);
|
||||
execRequest.setUsername(user.getUsername());
|
||||
execRequest.setSource(ExecSourceEnum.BATCH.name());
|
||||
execRequest.setExecMode(ExecModeEnum.MANUAL.name());
|
||||
// 调用执行
|
||||
return this.execCommandWithSource(execRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ExecLogVO reExecCommand(Long logId) {
|
||||
log.info("ExecService.reExecCommand start logId: {}", logId);
|
||||
// 获取执行记录
|
||||
ExecLogDO execLog = execLogDAO.selectByIdSource(logId, ExecSourceEnum.BATCH.name());
|
||||
Valid.notNull(execLog, ErrorMessage.DATA_ABSENT);
|
||||
// 获取执行主机
|
||||
List<ExecHostLogDO> hostLogs = execHostLogDAO.selectByLogId(logId);
|
||||
Valid.notEmpty(hostLogs, ErrorMessage.DATA_ABSENT);
|
||||
List<Long> hostIdList = hostLogs.stream()
|
||||
.map(ExecHostLogDO::getHostId)
|
||||
.collect(Collectors.toList());
|
||||
// 调用创建任务
|
||||
ExecCommandRequest request = ExecCommandRequest.builder()
|
||||
.description(execLog.getDescription())
|
||||
.timeout(execLog.getTimeout())
|
||||
.scriptExec(execLog.getScriptExec())
|
||||
.command(execLog.getCommand())
|
||||
.parameterSchema(execLog.getParameterSchema())
|
||||
.hostIdList(hostIdList)
|
||||
.build();
|
||||
// 调用执行
|
||||
return SpringHolder.getBean(ExecCommandService.class).execCommand(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecLogVO execCommandWithSource(ExecCommandExecDTO request) {
|
||||
log.info("ExecService.execCommandWithSource start params: {}", JSON.toJSONString(request));
|
||||
// 上下文调用执行
|
||||
ExecLogVO result = SpringHolder.getBean(ExecCommandService.class).createCommandWithSource(request);
|
||||
// 执行命令
|
||||
ExecTaskExecutors.start(result.getId(), Lists.map(result.getHosts(), ExecHostLogVO::getId));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
|
||||
public ExecLogVO createCommandWithSource(ExecCommandExecDTO request) {
|
||||
log.info("ExecService.createCommandWithSource start params: {}", JSON.toJSONString(request));
|
||||
String command = request.getCommand();
|
||||
List<Long> hostIdList = request.getHostIdList();
|
||||
// 查询主机信息
|
||||
List<HostDO> hosts = hostDAO.selectBatchIds(hostIdList);
|
||||
// 查询主机配置
|
||||
// TODO 待优化
|
||||
Map<Long, HostSshConfigModel> hostConfigMap = hostConfigService.buildHostConfigMap(hosts, HostTypeEnum.SSH);
|
||||
// 插入日志
|
||||
ExecLogDO execLog = ExecLogDO.builder()
|
||||
.userId(request.getUserId())
|
||||
@@ -166,17 +169,19 @@ public class ExecCommandServiceImpl implements ExecCommandService {
|
||||
.build();
|
||||
execLogDAO.insert(execLog);
|
||||
Long execId = execLog.getId();
|
||||
// 获取内置参数
|
||||
Map<String, Object> builtinParams = this.getBaseBuiltinParams(execId, request);
|
||||
// 设置主机日志
|
||||
List<ExecHostLogDO> execHostLogs = hosts.stream()
|
||||
.map(s -> this.convertExecHostLog(s, execLog, hostConfigMap.get(s.getId()), builtinParams))
|
||||
.map(s -> ExecHostLogDO.builder()
|
||||
.logId(execLog.getId())
|
||||
.hostId(s.getId())
|
||||
.hostName(s.getName())
|
||||
.hostAddress(s.getAddress())
|
||||
.status(ExecHostStatusEnum.WAITING.name())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
execHostLogDAO.insertBatch(execHostLogs);
|
||||
// 操作日志
|
||||
OperatorLogs.add(OperatorLogs.LOG_ID, execId);
|
||||
// 开始执行
|
||||
this.startExec(execLog, execHostLogs, hostConfigMap);
|
||||
// 返回
|
||||
ExecLogVO result = ExecLogConvert.MAPPER.to(execLog);
|
||||
List<ExecHostLogVO> resultHosts = ExecHostLogConvert.MAPPER.to(execHostLogs);
|
||||
@@ -184,218 +189,4 @@ public class ExecCommandServiceImpl implements ExecCommandService {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ExecLogVO reExecCommand(Long logId) {
|
||||
log.info("ExecService.reExecCommand start logId: {}", logId);
|
||||
// 获取执行记录
|
||||
ExecLogDO execLog = execLogDAO.selectByIdSource(logId, ExecSourceEnum.BATCH.name());
|
||||
Valid.notNull(execLog, ErrorMessage.DATA_ABSENT);
|
||||
// 获取执行主机
|
||||
List<ExecHostLogDO> hostLogs = execHostLogDAO.selectByLogId(logId);
|
||||
Valid.notEmpty(hostLogs, ErrorMessage.DATA_ABSENT);
|
||||
List<Long> hostIdList = hostLogs.stream()
|
||||
.map(ExecHostLogDO::getHostId)
|
||||
.collect(Collectors.toList());
|
||||
// 调用执行方法
|
||||
ExecCommandRequest request = ExecCommandRequest.builder()
|
||||
.description(execLog.getDescription())
|
||||
.timeout(execLog.getTimeout())
|
||||
.scriptExec(execLog.getScriptExec())
|
||||
.command(execLog.getCommand())
|
||||
.parameterSchema(execLog.getParameterSchema())
|
||||
.hostIdList(hostIdList)
|
||||
.build();
|
||||
return this.execCommand(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始执行命令
|
||||
*
|
||||
* @param execLog execLog
|
||||
* @param execHostLogs hostLogs
|
||||
* @param hostConfigMap hostConfigMap
|
||||
*/
|
||||
private void startExec(ExecLogDO execLog,
|
||||
List<ExecHostLogDO> execHostLogs,
|
||||
Map<Long, HostSshConfigModel> hostConfigMap) {
|
||||
// 执行主机
|
||||
List<ExecCommandHostDTO> hosts = execHostLogs.stream()
|
||||
.map(s -> {
|
||||
HostSshConfigModel config = hostConfigMap.get(s.getHostId());
|
||||
return ExecCommandHostDTO.builder()
|
||||
.hostId(s.getHostId())
|
||||
.hostLogId(s.getId())
|
||||
.hostName(s.getHostName())
|
||||
.hostAddress(s.getHostAddress())
|
||||
.command(s.getCommand())
|
||||
.logPath(s.getLogPath())
|
||||
.scriptPath(s.getScriptPath())
|
||||
.username(config.getUsername())
|
||||
.charset(config.getCharset())
|
||||
.fileNameCharset(config.getFileNameCharset())
|
||||
.fileContentCharset(config.getFileContentCharset())
|
||||
.build();
|
||||
}).collect(Collectors.toList());
|
||||
// 执行信息
|
||||
ExecCommandDTO exec = ExecCommandDTO.builder()
|
||||
.logId(execLog.getId())
|
||||
.userId(execLog.getUserId())
|
||||
.username(execLog.getUsername())
|
||||
.description(execLog.getDescription())
|
||||
.execSeq(execLog.getExecSeq())
|
||||
.timeout(execLog.getTimeout())
|
||||
.scriptExec(ScriptExecEnum.isEnabled(execLog.getScriptExec()))
|
||||
.hosts(hosts)
|
||||
.build();
|
||||
// 开始执行
|
||||
ExecTaskExecutors.start(exec);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 execHostLog
|
||||
*
|
||||
* @param host host
|
||||
* @param execLog execLog
|
||||
* @param config config
|
||||
* @param builtinParams builtinParams
|
||||
* @return execHostLog
|
||||
*/
|
||||
private ExecHostLogDO convertExecHostLog(HostDO host,
|
||||
ExecLogDO execLog,
|
||||
HostSshConfigModel config,
|
||||
Map<String, Object> builtinParams) {
|
||||
Long execId = execLog.getId();
|
||||
Long hostId = host.getId();
|
||||
// 脚本路径
|
||||
String scriptPath = null;
|
||||
if (ScriptExecEnum.isEnabled(execLog.getScriptExec())) {
|
||||
scriptPath = this.buildScriptPath(config.getUsername(), host.getOsType(), execId, hostId);
|
||||
}
|
||||
// 获取参数
|
||||
String parameter = JSON.toJSONString(this.getHostParams(builtinParams, host, config, scriptPath));
|
||||
return ExecHostLogDO.builder()
|
||||
.logId(execId)
|
||||
.hostId(hostId)
|
||||
.hostName(host.getName())
|
||||
.hostAddress(host.getAddress())
|
||||
.status(ExecHostStatusEnum.WAITING.name())
|
||||
.command(FORMATTER.format(execLog.getCommand(), parameter))
|
||||
.parameter(parameter)
|
||||
.logPath(this.buildLogPath(execId, hostId))
|
||||
.scriptPath(scriptPath)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础内置参数
|
||||
*
|
||||
* @param execId execId
|
||||
* @param request request
|
||||
* @return params
|
||||
*/
|
||||
private Map<String, Object> getBaseBuiltinParams(Long execId, ExecCommandExecDTO request) {
|
||||
String uuid = UUIds.random();
|
||||
Date date = new Date();
|
||||
// 输入参数
|
||||
Map<String, Object> params = this.extraSchemaParams(request.getParameterSchema());
|
||||
// 添加内置参数
|
||||
params.put("userId", request.getUserId());
|
||||
params.put("username", request.getUsername());
|
||||
params.put("source", request.getSource());
|
||||
params.put("sourceId", request.getSourceId());
|
||||
params.put("seq", request.getExecSeq());
|
||||
params.put("execId", execId);
|
||||
params.put("scriptExec", request.getScriptExec());
|
||||
params.put("uuid", uuid);
|
||||
params.put("uuidShort", uuid.replace("-", Strings.EMPTY));
|
||||
params.put("timestampMillis", date.getTime());
|
||||
params.put("timestamp", date.getTime() / Dates.SECOND_STAMP);
|
||||
params.put("date", Dates.format(date, Dates.YMD));
|
||||
params.put("datetime", Dates.format(date, Dates.YMD_HMS));
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主机参数
|
||||
*
|
||||
* @param baseParams baseParams
|
||||
* @param host host
|
||||
* @param config config
|
||||
* @param scriptPath scriptPath
|
||||
* @return params
|
||||
*/
|
||||
private Map<String, Object> getHostParams(Map<String, Object> baseParams,
|
||||
HostDO host,
|
||||
HostSshConfigModel config,
|
||||
String scriptPath) {
|
||||
String uuid = UUIds.random();
|
||||
Map<String, Object> params = Maps.newMap(baseParams);
|
||||
params.put("hostId", host.getId());
|
||||
params.put("hostName", host.getName());
|
||||
params.put("hostCode", host.getCode());
|
||||
params.put("hostAddress", host.getAddress());
|
||||
params.put("hostPort", host.getPort());
|
||||
params.put("hostUuid", uuid);
|
||||
params.put("hostUuidShort", uuid.replace("-", Strings.EMPTY));
|
||||
params.put("hostUsername", config.getUsername());
|
||||
params.put("osType", host.getOsType());
|
||||
params.put("charset", config.getCharset());
|
||||
params.put("scriptPath", scriptPath);
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取参数
|
||||
*
|
||||
* @param parameterSchema parameterSchema
|
||||
* @return params
|
||||
*/
|
||||
private Map<String, Object> extraSchemaParams(String parameterSchema) {
|
||||
List<ExecParameterSchemaDTO> schemaList = JSON.parseArray(parameterSchema, ExecParameterSchemaDTO.class);
|
||||
if (Lists.isEmpty(schemaList)) {
|
||||
return Maps.newMap();
|
||||
}
|
||||
// 解析参数
|
||||
return schemaList.stream()
|
||||
.collect(Collectors.toMap(ExecParameterSchemaDTO::getName,
|
||||
s -> {
|
||||
Object value = s.getValue();
|
||||
if (value == null) {
|
||||
value = Const.EMPTY;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
Functions.right()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建日志路径
|
||||
*
|
||||
* @param logId logId
|
||||
* @param hostId hostId
|
||||
* @return logPath
|
||||
*/
|
||||
private String buildLogPath(Long logId, Long hostId) {
|
||||
return logsFileClient.getReturnPath(EndpointDefine.EXEC_LOG.format(logId, hostId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 侯建脚本路径
|
||||
*
|
||||
* @param username username
|
||||
* @param osType osType
|
||||
* @param logId logId
|
||||
* @param hostId hostId
|
||||
* @return scriptPath
|
||||
*/
|
||||
private String buildScriptPath(String username, String osType, Long logId, Long hostId) {
|
||||
HostOsTypeEnum os = HostOsTypeEnum.of(osType);
|
||||
String name = FileConst.EXEC
|
||||
+ "/" + logId
|
||||
+ "/" + hostId
|
||||
+ os.getScriptSuffix();
|
||||
return PathUtils.buildAppPath(HostOsTypeEnum.WINDOWS.equals(os), username, FileConst.SCRIPT, name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public class ExecHostLogServiceImpl implements ExecHostLogService {
|
||||
.map(execTaskManager::getTask)
|
||||
.map(IExecTaskHandler::getHandlers)
|
||||
.flatMap(s -> s.stream()
|
||||
.filter(h -> h.getHostId().equals(record.getHostId()))
|
||||
.filter(h -> id.equals(h.getId()))
|
||||
.findFirst())
|
||||
.ifPresent(IExecCommandHandler::interrupt);
|
||||
// 删除
|
||||
|
||||
@@ -27,6 +27,7 @@ import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.collect.Lists;
|
||||
import cn.orionsec.kit.lang.utils.time.cron.Cron;
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -49,7 +50,10 @@ import org.dromara.visor.module.asset.entity.request.exec.*;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecJobVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.HostBaseVO;
|
||||
import org.dromara.visor.module.asset.enums.*;
|
||||
import org.dromara.visor.module.asset.enums.ExecJobStatusEnum;
|
||||
import org.dromara.visor.module.asset.enums.ExecModeEnum;
|
||||
import org.dromara.visor.module.asset.enums.ExecSourceEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.job.ExecCommandJob;
|
||||
import org.dromara.visor.module.asset.service.AssetAuthorizedDataService;
|
||||
import org.dromara.visor.module.asset.service.ExecCommandService;
|
||||
@@ -57,6 +61,7 @@ import org.dromara.visor.module.asset.service.ExecJobHostService;
|
||||
import org.dromara.visor.module.asset.service.ExecJobService;
|
||||
import org.dromara.visor.module.infra.api.SystemUserApi;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -107,7 +112,6 @@ public class ExecJobServiceImpl implements ExecJobService {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
// 验证表达式是否正确
|
||||
Cron.of(request.getExpression());
|
||||
Valid.valid(ScriptExecEnum::of, request.getScriptExec());
|
||||
// 转换
|
||||
ExecJobDO record = ExecJobConvert.MAPPER.to(request);
|
||||
// 查询数据是否冲突
|
||||
@@ -137,7 +141,6 @@ public class ExecJobServiceImpl implements ExecJobService {
|
||||
log.info("ExecJobService-updateExecJobById id: {}, request: {}", id, JSON.toJSONString(request));
|
||||
// 验证表达式是否正确
|
||||
Cron.of(request.getExpression());
|
||||
Valid.valid(ScriptExecEnum::of, request.getScriptExec());
|
||||
// 查询
|
||||
ExecJobDO record = execJobDAO.selectById(id);
|
||||
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
|
||||
@@ -164,7 +167,6 @@ public class ExecJobServiceImpl implements ExecJobService {
|
||||
public Integer updateExecJobStatus(ExecJobUpdateStatusRequest request) {
|
||||
Long id = request.getId();
|
||||
ExecJobStatusEnum status = ExecJobStatusEnum.of(request.getStatus());
|
||||
Valid.notNull(status, ErrorMessage.PARAM_ERROR);
|
||||
log.info("ExecJobService-updateExecJobStatus id: {}, status: {}", id, status);
|
||||
// 查询任务
|
||||
ExecJobDO record = execJobDAO.selectById(id);
|
||||
@@ -203,7 +205,7 @@ public class ExecJobServiceImpl implements ExecJobService {
|
||||
// 设置日志参数
|
||||
OperatorLogs.add(OperatorLogs.NAME, job.getName());
|
||||
OperatorLogs.add(OperatorLogs.USERNAME, username);
|
||||
log.info("ExecJobService-setExecJobExecUser effect: {}", effect);
|
||||
log.info("ExecJobService-updateExecJobExecUser effect: {}", effect);
|
||||
return effect;
|
||||
}
|
||||
|
||||
@@ -320,7 +322,6 @@ public class ExecJobServiceImpl implements ExecJobService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void manualTriggerExecJob(Long id) {
|
||||
log.info("ExecJobService.manualTriggerExecJob start id: {}", id);
|
||||
// 查询任务
|
||||
@@ -336,12 +337,12 @@ public class ExecJobServiceImpl implements ExecJobService {
|
||||
request.setUserId(user.getId());
|
||||
request.setUsername(user.getUsername());
|
||||
}
|
||||
// 触发任务
|
||||
this.triggerExecJob(request, job);
|
||||
// 上下文触发任务
|
||||
SpringHolder.getBean(ExecJobService.class).triggerExecJob(request, job);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
|
||||
public void triggerExecJob(ExecJobTriggerRequest request, ExecJobDO job) {
|
||||
Long id = request.getId();
|
||||
// 查询任务主机
|
||||
|
||||
@@ -328,7 +328,7 @@ public class ExecLogServiceImpl implements ExecLogService {
|
||||
log.info("ExecLogService.interruptHostExec interrupt logId: {}, hostLogId: {}", logId, hostLogId);
|
||||
IExecCommandHandler handler = task.getHandlers()
|
||||
.stream()
|
||||
.filter(s -> s.getHostId().equals(hostLog.getHostId()))
|
||||
.filter(s -> hostLogId.equals(s.getId()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
// 中断
|
||||
|
||||
@@ -234,6 +234,7 @@ public class TerminalServiceImpl implements TerminalService {
|
||||
TerminalConnectDTO conn = new TerminalConnectDTO();
|
||||
conn.setHostId(host.getId());
|
||||
conn.setHostName(host.getName());
|
||||
conn.setHostCode(host.getCode());
|
||||
conn.setHostAddress(host.getAddress());
|
||||
conn.setHostPort(host.getPort());
|
||||
conn.setOsType(host.getOsType());
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.asset.utils;
|
||||
|
||||
import cn.orionsec.kit.lang.function.Functions;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.collect.Lists;
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import cn.orionsec.kit.lang.utils.json.matcher.NoMatchStrategy;
|
||||
import cn.orionsec.kit.lang.utils.json.matcher.ReplacementFormatter;
|
||||
import cn.orionsec.kit.lang.utils.json.matcher.ReplacementFormatters;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.module.asset.entity.dto.ExecParameterSchemaDTO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 执行工具类
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2025/1/16 17:28
|
||||
*/
|
||||
public class ExecUtils {
|
||||
|
||||
private static final ReplacementFormatter FORMATTER = ReplacementFormatters.create("@{{ ", " }}")
|
||||
.noMatchStrategy(NoMatchStrategy.KEEP);
|
||||
|
||||
private ExecUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换命令
|
||||
*
|
||||
* @param command command
|
||||
* @param params params
|
||||
* @return command
|
||||
*/
|
||||
public static String format(String command, Map<String, Object> params) {
|
||||
return Strings.replaceCRLF(FORMATTER.format(command, params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取参数
|
||||
*
|
||||
* @param parameterSchema parameterSchema
|
||||
* @return params
|
||||
*/
|
||||
public static Map<String, Object> extraSchemaParams(String parameterSchema) {
|
||||
List<ExecParameterSchemaDTO> schemaList = JSON.parseArray(parameterSchema, ExecParameterSchemaDTO.class);
|
||||
if (Lists.isEmpty(schemaList)) {
|
||||
return Maps.newMap();
|
||||
}
|
||||
// 解析参数
|
||||
return schemaList.stream()
|
||||
.collect(Collectors.toMap(ExecParameterSchemaDTO::getName,
|
||||
s -> {
|
||||
Object value = s.getValue();
|
||||
if (value == null) {
|
||||
value = Const.EMPTY;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
Functions.right()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "app.tracker",
|
||||
"type": "org.dromara.visor.module.asset.define.config.AppTrackerConfig",
|
||||
"sourceType": "org.dromara.visor.module.asset.define.config.AppTrackerConfig"
|
||||
},
|
||||
{
|
||||
"name": "app.sftp",
|
||||
"type": "org.dromara.visor.module.asset.define.config.AppSftpConfig",
|
||||
"sourceType": "org.dromara.visor.module.asset.define.config.AppSftpConfig"
|
||||
},
|
||||
{
|
||||
"name": "app.exec-log",
|
||||
"type": "org.dromara.visor.module.asset.define.config.AppExecLogConfig",
|
||||
"sourceType": "org.dromara.visor.module.asset.define.config.AppExecLogConfig"
|
||||
},
|
||||
{
|
||||
"name": "app.auto-clear.exec-log",
|
||||
"type": "org.dromara.visor.module.asset.define.config.AppExecLogAutoClearConfig",
|
||||
"sourceType": "org.dromara.visor.module.asset.define.config.AppExecLogAutoClearConfig"
|
||||
},
|
||||
{
|
||||
"name": "app.auto-clear.terminal-connect-log",
|
||||
"type": "org.dromara.visor.module.asset.define.config.AppTerminalConnectLogAutoClearConfig",
|
||||
"sourceType": "org.dromara.visor.module.asset.define.config.AppTerminalConnectLogAutoClearConfig"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "app.tracker.offset",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "加载偏移量 (行).",
|
||||
"defaultValue": "300"
|
||||
},
|
||||
{
|
||||
"name": "app.tracker.delay",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "延迟时间 (ms).",
|
||||
"defaultValue": "100"
|
||||
},
|
||||
{
|
||||
"name": "app.tracker.wait-times",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "文件未找到等待次数.",
|
||||
"defaultValue": "100"
|
||||
},
|
||||
{
|
||||
"name": "app.sftp.upload-present-backup",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "上传文件时 文件存在是否备份.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "app.sftp.backup-file-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "备份文件名称.",
|
||||
"defaultValue": "bk_${fileName}_${timestamp}"
|
||||
},
|
||||
{
|
||||
"name": "app.exec-log.append-ansi",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "是否拼接 ansi 执行状态日志.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "app.auto-clear.exec-log.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "开启 批量执行日志自动清理."
|
||||
},
|
||||
{
|
||||
"name": "app.auto-clear.exec-log.keep-period",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "批量执行日志自动清理 保留周期 (天)."
|
||||
},
|
||||
{
|
||||
"name": "app.auto-clear.terminal-connect-log.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "开启 终端连接日志自动清理."
|
||||
},
|
||||
{
|
||||
"name": "app.auto-clear.terminal-connect-log.keep-period",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "终端连接日志自动清理 保留周期 (天)."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,71 +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.define.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 应用认证配置
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/3/5 18:26
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties("app.authentication")
|
||||
public class AppAuthenticationConfig {
|
||||
|
||||
/**
|
||||
* 是否允许多端登录
|
||||
*/
|
||||
private Boolean allowMultiDevice;
|
||||
|
||||
/**
|
||||
* 是否允许凭证续签
|
||||
*/
|
||||
private Boolean allowRefresh;
|
||||
|
||||
/**
|
||||
* 凭证续签最大次数
|
||||
*/
|
||||
private Integer maxRefreshCount;
|
||||
|
||||
/**
|
||||
* 登录失败发送站内信阈值
|
||||
*/
|
||||
private Integer loginFailedSendThreshold;
|
||||
|
||||
/**
|
||||
* 登录失败锁定次数
|
||||
*/
|
||||
private Integer loginFailedLockCount;
|
||||
|
||||
/**
|
||||
* 登录失败锁定时间 (分)
|
||||
*/
|
||||
private Integer loginFailedLockTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.define.config;
|
||||
|
||||
import org.dromara.visor.common.config.ConfigRef;
|
||||
import org.dromara.visor.common.config.ConfigStore;
|
||||
import org.dromara.visor.common.constant.ConfigKeys;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 应用登录配置
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/3/5 18:26
|
||||
*/
|
||||
@Component
|
||||
public class AppLoginConfig {
|
||||
|
||||
/**
|
||||
* 凭证有效期(分)
|
||||
*/
|
||||
private final ConfigRef<Integer> loginSessionTime;
|
||||
|
||||
/**
|
||||
* 是否允许多端登录
|
||||
*/
|
||||
private final ConfigRef<Boolean> allowMultiDevice;
|
||||
|
||||
/**
|
||||
* 是否允许凭证续签
|
||||
*/
|
||||
private final ConfigRef<Boolean> allowRefresh;
|
||||
|
||||
/**
|
||||
* 凭证续签最大次数
|
||||
*/
|
||||
private final ConfigRef<Integer> maxRefreshCount;
|
||||
|
||||
/**
|
||||
* 凭证续签间隔分
|
||||
*/
|
||||
private final ConfigRef<Integer> refreshInterval;
|
||||
|
||||
/**
|
||||
* 登录失败是否锁定
|
||||
*/
|
||||
private final ConfigRef<Boolean> loginFailedLock;
|
||||
|
||||
/**
|
||||
* 登录失败锁定阈值
|
||||
*/
|
||||
private final ConfigRef<Integer> loginFailedLockThreshold;
|
||||
|
||||
/**
|
||||
* 登录失败锁定时间 (分)
|
||||
*/
|
||||
private final ConfigRef<Integer> loginFailedLockTime;
|
||||
|
||||
/**
|
||||
* 登录失败发信
|
||||
*/
|
||||
private final ConfigRef<Boolean> loginFailedSend;
|
||||
|
||||
/**
|
||||
* 登录失败发送站内信阈值
|
||||
*/
|
||||
private final ConfigRef<Integer> loginFailedSendThreshold;
|
||||
|
||||
public AppLoginConfig(ConfigStore configStore) {
|
||||
this.loginSessionTime = configStore.int32(ConfigKeys.LOGIN_LOGIN_SESSION_TIME);
|
||||
this.allowMultiDevice = configStore.bool(ConfigKeys.LOGIN_ALLOW_MULTI_DEVICE);
|
||||
this.allowRefresh = configStore.bool(ConfigKeys.LOGIN_ALLOW_REFRESH);
|
||||
this.maxRefreshCount = configStore.int32(ConfigKeys.LOGIN_MAX_REFRESH_COUNT);
|
||||
this.refreshInterval = configStore.int32(ConfigKeys.LOGIN_REFRESH_INTERVAL);
|
||||
this.loginFailedLock = configStore.bool(ConfigKeys.LOGIN_LOGIN_FAILED_LOCK);
|
||||
this.loginFailedLockThreshold = configStore.int32(ConfigKeys.LOGIN_LOGIN_FAILED_LOCK_THRESHOLD);
|
||||
this.loginFailedLockTime = configStore.int32(ConfigKeys.LOGIN_LOGIN_FAILED_LOCK_TIME);
|
||||
this.loginFailedSend = configStore.bool(ConfigKeys.LOGIN_LOGIN_FAILED_SEND);
|
||||
this.loginFailedSendThreshold = configStore.int32(ConfigKeys.LOGIN_LOGIN_FAILED_SEND_THRESHOLD);
|
||||
}
|
||||
|
||||
public Integer getLoginSessionTime() {
|
||||
return loginSessionTime.value;
|
||||
}
|
||||
|
||||
public Boolean getAllowMultiDevice() {
|
||||
return allowMultiDevice.value;
|
||||
}
|
||||
|
||||
public Boolean getAllowRefresh() {
|
||||
return allowRefresh.value;
|
||||
}
|
||||
|
||||
public Integer getMaxRefreshCount() {
|
||||
return maxRefreshCount.value;
|
||||
}
|
||||
|
||||
public Integer getRefreshInterval() {
|
||||
return refreshInterval.value;
|
||||
}
|
||||
|
||||
public Boolean getLoginFailedLock() {
|
||||
return loginFailedLock.value;
|
||||
}
|
||||
|
||||
public Integer getLoginFailedLockThreshold() {
|
||||
return loginFailedLockThreshold.value;
|
||||
}
|
||||
|
||||
public Integer getLoginFailedLockTime() {
|
||||
return loginFailedLockTime.value;
|
||||
}
|
||||
|
||||
public Boolean getLoginFailedSend() {
|
||||
return loginFailedSend.value;
|
||||
}
|
||||
|
||||
public Integer getLoginFailedSendThreshold() {
|
||||
return loginFailedSendThreshold.value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +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.handler.setting.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* SFTP 系统设置模型
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/10/9 11:45
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SftpSystemSettingModel {
|
||||
|
||||
/**
|
||||
* 预览大小
|
||||
*/
|
||||
private Integer previewSize;
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "app.authentication",
|
||||
"type": "org.dromara.visor.module.infra.define.config.AppAuthenticationConfig",
|
||||
"sourceType": "org.dromara.visor.module.infra.define.config.AppAuthenticationConfig"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "app.authentication.allowMultiDevice",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "是否允许多端登录."
|
||||
},
|
||||
{
|
||||
"name": "app.authentication.allowRefresh",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "是否允许凭证续签."
|
||||
},
|
||||
{
|
||||
"name": "app.authentication.maxRefreshCount",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "凭证续签最大次数."
|
||||
},
|
||||
{
|
||||
"name": "app.authentication.loginFailedSendThreshold",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "登录失败发送站内信阈值."
|
||||
},
|
||||
{
|
||||
"name": "app.authentication.loginFailedLockCount",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "登录失败锁定次数."
|
||||
},
|
||||
{
|
||||
"name": "app.authentication.loginFailedLockTime",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "登录失败锁定时间 (分)."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,35 +1,15 @@
|
||||
import axios from 'axios';
|
||||
import { dateFormat } from '@/utils';
|
||||
|
||||
/**
|
||||
* 系统设置类型
|
||||
*/
|
||||
export type SystemSettingType = 'SFTP' | 'ENCRYPT';
|
||||
|
||||
/**
|
||||
* 系统设置更新请求
|
||||
*/
|
||||
export interface SystemSettingUpdateRequest {
|
||||
type?: SystemSettingType;
|
||||
item?: string;
|
||||
value?: any;
|
||||
type?: string;
|
||||
value?: string;
|
||||
settings?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用信息查询响应
|
||||
*/
|
||||
export interface SystemLicenseResponse {
|
||||
userCount: number;
|
||||
hostCount: number;
|
||||
release: string;
|
||||
releaseName: string;
|
||||
issueDate: number;
|
||||
expireDate: number;
|
||||
expireDay: number;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用信息查询响应
|
||||
*/
|
||||
@@ -47,28 +27,73 @@ export interface AppReleaseResponse {
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统聚合设置响应
|
||||
* RSA 密钥对响应
|
||||
*/
|
||||
export interface SystemSettingAggregateResponse {
|
||||
sftp: SftpSetting;
|
||||
encrypt: EncryptSetting;
|
||||
}
|
||||
|
||||
/**
|
||||
* SFTP 配置
|
||||
*/
|
||||
export interface SftpSetting {
|
||||
previewSize: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密配置
|
||||
*/
|
||||
export interface EncryptSetting {
|
||||
export interface RsaKeyPairResponse {
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统设置
|
||||
*/
|
||||
export type SystemSetting = SftpSetting
|
||||
& LoginSetting & EncryptSetting
|
||||
& LogSetting & AutoClearSetting;
|
||||
|
||||
/**
|
||||
* SFTP 设置
|
||||
*/
|
||||
export interface SftpSetting {
|
||||
sftp_previewSize: number;
|
||||
sftp_uploadPresentBackup: string;
|
||||
sftp_uploadBackupFileName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录设置
|
||||
*/
|
||||
export interface LoginSetting {
|
||||
login_allowMultiDevice: string;
|
||||
login_allowRefresh: string;
|
||||
login_maxRefreshCount: number;
|
||||
login_refreshInterval: number;
|
||||
login_loginFailedLock: string;
|
||||
login_loginFailedLockThreshold: number;
|
||||
login_loginFailedLockTime: number;
|
||||
login_loginFailedSend: string;
|
||||
login_loginFailedSendThreshold: number;
|
||||
login_loginSessionTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密设置
|
||||
*/
|
||||
export interface EncryptSetting {
|
||||
encrypt_publicKey: string;
|
||||
encrypt_privateKey: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 日志设置
|
||||
*/
|
||||
export interface LogSetting {
|
||||
log_webScrollLines: number;
|
||||
log_trackerLoadLines: number;
|
||||
log_trackerLoadInterval: number;
|
||||
log_execDetailLog: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动清理设置
|
||||
*/
|
||||
export interface AutoClearSetting {
|
||||
autoClear_execLogEnabled: string;
|
||||
autoClear_execLogKeepDays: number;
|
||||
autoClear_terminalLogEnabled: string;
|
||||
autoClear_terminalLogKeepDays: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询应用信息
|
||||
*/
|
||||
@@ -80,7 +105,7 @@ export function getSystemAppInfo() {
|
||||
* 获取系统聚合设置
|
||||
*/
|
||||
export function getSystemAggregateSetting() {
|
||||
return axios.get<SystemSettingAggregateResponse>('/infra/system-setting/setting');
|
||||
return axios.get<Record<keyof SystemSetting, string>>('/infra/system-setting/setting');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,26 +126,26 @@ export function getAppLatestRelease() {
|
||||
* 生成密钥对
|
||||
*/
|
||||
export function generatorKeypair() {
|
||||
return axios.get<EncryptSetting>('/infra/system-setting/generator-keypair');
|
||||
return axios.get<RsaKeyPairResponse>('/infra/system-setting/generator-keypair');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统设置-单个
|
||||
*/
|
||||
export function updateSystemSetting(request: SystemSettingUpdateRequest) {
|
||||
return axios.put<number>('/infra/system-setting/update', request);
|
||||
return axios.put<string>('/infra/system-setting/update', request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统设置-多个
|
||||
*/
|
||||
export function updateSystemSettingBatch(request: SystemSettingUpdateRequest) {
|
||||
return axios.put<number>('/infra/system-setting/update-batch', request);
|
||||
return axios.put<string>('/infra/system-setting/update-batch', request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询系统设置
|
||||
*/
|
||||
export function getSystemSetting<T>(type: SystemSettingType) {
|
||||
return axios.get<T>('/infra/system-setting/get', { params: { type } });
|
||||
export function getSystemSetting(type: string) {
|
||||
return axios.get<Record<keyof SystemSetting, string>>('/infra/system-setting/get', { params: { type } });
|
||||
}
|
||||
|
||||
@@ -129,6 +129,21 @@ export function formatDuration(start: number, end?: number): string {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转为匿名数字 number | undefined
|
||||
*/
|
||||
export function toAnonymousNumber(value: string | undefined): number {
|
||||
if (value === undefined || value === null) {
|
||||
return value as unknown as number;
|
||||
}
|
||||
const num = Number.parseInt(value);
|
||||
if (Number.isNaN(num)) {
|
||||
return undefined as unknown as number;
|
||||
} else {
|
||||
return num;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字为 ,分割
|
||||
*/
|
||||
|
||||
@@ -12,7 +12,7 @@ export const encrypt = async (data: string | undefined): Promise<string | undefi
|
||||
return data;
|
||||
}
|
||||
// 获取公钥
|
||||
const publicKey = (await useCacheStore().loadSystemSetting()).encrypt?.publicKey;
|
||||
const publicKey = (await useCacheStore().loadSystemSetting()).encrypt_publicKey;
|
||||
const encryptor = new JSEncrypt();
|
||||
encryptor.setPublicKey(publicKey);
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<a-spin class="main-container" :loading="loading">
|
||||
<!-- 标题 -->
|
||||
<h3 class="setting-header">自动清理设置</h3>
|
||||
<!-- 表单 -->
|
||||
<a-form :model="setting"
|
||||
ref="formRef"
|
||||
class="setting-form"
|
||||
label-align="right"
|
||||
:auto-label-width="true">
|
||||
<!-- 自动清理执行记录 -->
|
||||
<a-form-item field="autoClear_execLogEnabled"
|
||||
label="自动清理执行记录"
|
||||
:rules="[{required: true, message: '请选择此项'}]"
|
||||
hide-asterisk>
|
||||
<a-switch v-model="setting.autoClear_execLogEnabled"
|
||||
type="round"
|
||||
checked-value="true"
|
||||
unchecked-value="false"
|
||||
checked-text="开启"
|
||||
unchecked-text="关闭" />
|
||||
<template #extra>
|
||||
开启后将会在每天凌晨自动清理命令执行记录
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 执行记录保留天数 -->
|
||||
<a-form-item field="autoClear_execLogKeepDays"
|
||||
label="执行记录保留天数"
|
||||
:rules="[{required: true, message: '请输入执行记录保留天数'}]"
|
||||
hide-asterisk>
|
||||
<a-input-number v-model="setting.autoClear_execLogKeepDays"
|
||||
class="input-wrapper"
|
||||
:min="0"
|
||||
:max="99999"
|
||||
placeholder="请输入执行记录保留天数"
|
||||
allow-clear
|
||||
hide-button>
|
||||
<template #suffix>
|
||||
天
|
||||
</template>
|
||||
</a-input-number>
|
||||
<template #extra>
|
||||
自动清理命令执行记录时保留的天数
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 自动清理终端记录 -->
|
||||
<a-form-item field="autoClear_terminalLogEnabled"
|
||||
label="自动清理终端记录"
|
||||
:rules="[{required: true, message: '请选择此项'}]"
|
||||
hide-asterisk>
|
||||
<a-switch v-model="setting.autoClear_terminalLogEnabled"
|
||||
type="round"
|
||||
checked-value="true"
|
||||
unchecked-value="false"
|
||||
checked-text="开启"
|
||||
unchecked-text="关闭" />
|
||||
<template #extra>
|
||||
开启后将会在每天凌晨自动清理终端连接记录
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 终端记录保留天数 -->
|
||||
<a-form-item field="autoClear_terminalLogKeepDays"
|
||||
label="终端记录保留天数"
|
||||
:rules="[{required: true, message: '请输入终端记录保留天数'}]"
|
||||
hide-asterisk>
|
||||
<a-input-number v-model="setting.autoClear_terminalLogKeepDays"
|
||||
class="input-wrapper"
|
||||
:min="0"
|
||||
:max="99999"
|
||||
placeholder="请输入终端记录保留天数"
|
||||
allow-clear
|
||||
hide-button>
|
||||
<template #suffix>
|
||||
天
|
||||
</template>
|
||||
</a-input-number>
|
||||
<template #extra>
|
||||
自动清理终端连接记录时保留的天数
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 按钮 -->
|
||||
<a-form-item v-permission="['infra:system-setting:update']">
|
||||
<!-- 保存 -->
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
@click="save">
|
||||
保存
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'autoClearSetting',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { AutoClearSetting } from '@/api/system/setting';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { getSystemSetting, updateSystemSettingBatch } from '@/api/system/setting';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { toAnonymousNumber } from '@/utils';
|
||||
import { SystemSettingTypes } from '../types/const';
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
|
||||
const formRef = ref();
|
||||
const setting = ref<AutoClearSetting>({} as AutoClearSetting);
|
||||
|
||||
// 保存
|
||||
const save = async () => {
|
||||
// 验证参数
|
||||
const error = await formRef.value.validate();
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
await updateSystemSettingBatch({
|
||||
type: SystemSettingTypes.AUTO_CLEAR,
|
||||
settings: setting.value
|
||||
});
|
||||
Message.success('修改成功');
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载配置
|
||||
onMounted(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await getSystemSetting(SystemSettingTypes.AUTO_CLEAR);
|
||||
setting.value = {
|
||||
...data,
|
||||
autoClear_execLogKeepDays: toAnonymousNumber(data.autoClear_execLogKeepDays),
|
||||
autoClear_terminalLogKeepDays: toAnonymousNumber(data.autoClear_terminalLogKeepDays),
|
||||
};
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.input-wrapper {
|
||||
width: 368px;
|
||||
}
|
||||
</style>
|
||||
@@ -13,22 +13,22 @@
|
||||
<a-alert>请输入 PKCS8 格式的 RSA Base64 密钥, 用于前后端传输时的数据加密</a-alert>
|
||||
</a-form-item>
|
||||
<!-- 加密公钥 -->
|
||||
<a-form-item field="publicKey"
|
||||
<a-form-item field="encrypt_publicKey"
|
||||
label="加密公钥"
|
||||
:rules="[{required: true, message: '请输入加密公钥'}]"
|
||||
hide-asterisk>
|
||||
<a-textarea v-model="setting.publicKey"
|
||||
<a-textarea v-model="setting.encrypt_publicKey"
|
||||
class="input-wrapper"
|
||||
placeholder="RSA 公钥 Base64"
|
||||
:auto-size="{ minRows: 5, maxRows: 5 }"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 加密私钥 -->
|
||||
<a-form-item field="privateKey"
|
||||
<a-form-item field="encrypt_privateKey"
|
||||
label="加密私钥"
|
||||
:rules="[{required: true, message: '请输入加密私钥'}]"
|
||||
hide-asterisk>
|
||||
<a-textarea v-model="setting.privateKey"
|
||||
<a-textarea v-model="setting.encrypt_privateKey"
|
||||
class="input-wrapper"
|
||||
placeholder="RSA 私钥 Base64"
|
||||
:auto-size="{ minRows: 14, maxRows: 14 }"
|
||||
@@ -97,8 +97,8 @@
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await generatorKeypair();
|
||||
setting.value.publicKey = data.publicKey;
|
||||
setting.value.privateKey = data.privateKey;
|
||||
setting.value.encrypt_publicKey = data.publicKey;
|
||||
setting.value.encrypt_privateKey = data.privateKey;
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -109,8 +109,10 @@
|
||||
onMounted(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await getSystemSetting<EncryptSetting>(SystemSettingTypes.ENCRYPT);
|
||||
setting.value = data;
|
||||
const { data } = await getSystemSetting(SystemSettingTypes.ENCRYPT);
|
||||
setting.value = {
|
||||
...data
|
||||
};
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<a-spin class="main-container" :loading="loading">
|
||||
<!-- 标题 -->
|
||||
<h3 class="setting-header">日志设置</h3>
|
||||
<!-- 表单 -->
|
||||
<a-form :model="setting"
|
||||
ref="formRef"
|
||||
class="setting-form"
|
||||
label-align="right"
|
||||
:auto-label-width="true">
|
||||
<!-- 执行详细日志 -->
|
||||
<a-form-item field="log_execDetailLog"
|
||||
label="执行详细日志"
|
||||
:rules="[{required: true, message: '请选择此项'}]"
|
||||
hide-asterisk>
|
||||
<a-switch v-model="setting.log_execDetailLog"
|
||||
type="round"
|
||||
checked-value="true"
|
||||
unchecked-value="false"
|
||||
checked-text="详细输出"
|
||||
unchecked-text="原始输出" />
|
||||
<template #extra>
|
||||
开启后在命令执行时会展示详细的日志信息(执行主机、命令等), 关闭后则只显示命令的标准输出
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 最大显示行数 -->
|
||||
<a-form-item field="log_webScrollLines"
|
||||
label="最大显示行数"
|
||||
:rules="[{required: true, message: '请输入日志最大显示行数'}]"
|
||||
hide-asterisk>
|
||||
<a-input-number v-model="setting.log_webScrollLines"
|
||||
class="input-wrapper"
|
||||
:min="0"
|
||||
:max="999999"
|
||||
placeholder="请输入日志最大显示行数"
|
||||
allow-clear
|
||||
hide-button>
|
||||
<template #suffix>
|
||||
行
|
||||
</template>
|
||||
</a-input-number>
|
||||
<template #extra>
|
||||
前端日志组件最大显示的行数, 超出部分将会被覆盖 (数值越大内存占用越多)
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 日志加载行数 -->
|
||||
<a-form-item field="log_trackerLoadLines"
|
||||
label="日志加载行数"
|
||||
:rules="[{required: true, message: '请输入日志加载行数'}]"
|
||||
hide-asterisk>
|
||||
<a-input-number v-model="setting.log_trackerLoadLines"
|
||||
class="input-wrapper"
|
||||
:min="0"
|
||||
:max="99999"
|
||||
placeholder="请输入日志加载行数"
|
||||
allow-clear
|
||||
hide-button>
|
||||
<template #suffix>
|
||||
行
|
||||
</template>
|
||||
</a-input-number>
|
||||
<template #extra>
|
||||
当查看日志时, 默认读取的行数
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 日志监听间隔 -->
|
||||
<a-form-item field="log_trackerLoadInterval"
|
||||
label="日志监听间隔"
|
||||
:rules="[{required: true, message: '请输入日志监听间隔'}]"
|
||||
hide-asterisk>
|
||||
<a-input-number v-model="setting.log_trackerLoadInterval"
|
||||
class="input-wrapper"
|
||||
:min="0"
|
||||
:max="99999"
|
||||
placeholder="请输入日志监听间隔"
|
||||
allow-clear
|
||||
hide-button>
|
||||
<template #suffix>
|
||||
ms
|
||||
</template>
|
||||
</a-input-number>
|
||||
<template #extra>
|
||||
日志增量刷新的间隔 (毫秒)
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 按钮 -->
|
||||
<a-form-item v-permission="['infra:system-setting:update']">
|
||||
<!-- 保存 -->
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
@click="save">
|
||||
保存
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'logSetting',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { LogSetting } from '@/api/system/setting';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { getSystemSetting, updateSystemSettingBatch } from '@/api/system/setting';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { toAnonymousNumber } from '@/utils';
|
||||
import { SystemSettingTypes } from '../types/const';
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
|
||||
const formRef = ref();
|
||||
const setting = ref<LogSetting>({} as LogSetting);
|
||||
|
||||
// 保存
|
||||
const save = async () => {
|
||||
// 验证参数
|
||||
const error = await formRef.value.validate();
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
await updateSystemSettingBatch({
|
||||
type: SystemSettingTypes.LOG,
|
||||
settings: setting.value
|
||||
});
|
||||
Message.success('修改成功');
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载配置
|
||||
onMounted(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await getSystemSetting(SystemSettingTypes.LOG);
|
||||
setting.value = {
|
||||
...data,
|
||||
log_webScrollLines: toAnonymousNumber(data.log_webScrollLines),
|
||||
log_trackerLoadInterval: toAnonymousNumber(data.log_trackerLoadInterval),
|
||||
log_trackerLoadLines: toAnonymousNumber(data.log_trackerLoadLines),
|
||||
};
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.input-wrapper {
|
||||
width: 368px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<a-spin class="main-container" :loading="loading">
|
||||
<!-- 标题 -->
|
||||
<h3 class="setting-header">登录设置</h3>
|
||||
<!-- 表单 -->
|
||||
<a-form :model="setting"
|
||||
ref="formRef"
|
||||
class="setting-form"
|
||||
label-align="right"
|
||||
:auto-label-width="true">
|
||||
<!-- 允许多端登录 -->
|
||||
<a-form-item field="login_allowMultiDevice"
|
||||
label="允许多端登录"
|
||||
:rules="[{required: true, message: '请选择此项'}]"
|
||||
hide-asterisk>
|
||||
<a-switch v-model="setting.login_allowMultiDevice"
|
||||
type="round"
|
||||
checked-value="true"
|
||||
unchecked-value="false"
|
||||
checked-text="开启"
|
||||
unchecked-text="关闭" />
|
||||
<template #extra>
|
||||
开启后一个账号可以多个设备同时登录
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 允许凭证续签 -->
|
||||
<a-form-item field="login_allowRefresh"
|
||||
label="允许凭证续签"
|
||||
:rules="[{required: true, message: '请选择此项'}]"
|
||||
hide-asterisk>
|
||||
<a-switch v-model="setting.login_allowRefresh"
|
||||
type="round"
|
||||
checked-value="true"
|
||||
unchecked-value="false"
|
||||
checked-text="开启"
|
||||
unchecked-text="关闭" />
|
||||
<template #extra>
|
||||
开启后当凭证即将过期时,系统会自动续签
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 登录失败锁定 -->
|
||||
<a-form-item field="login_loginFailedLock"
|
||||
label="登录失败锁定"
|
||||
:rules="[{required: true, message: '请选择此项'}]"
|
||||
hide-asterisk>
|
||||
<a-switch v-model="setting.login_loginFailedLock"
|
||||
type="round"
|
||||
checked-value="true"
|
||||
unchecked-value="false"
|
||||
checked-text="开启"
|
||||
unchecked-text="关闭" />
|
||||
<template #extra>
|
||||
开启后当登录失败次数达到阈值时账号会自动锁定
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 登录失败发信 -->
|
||||
<a-form-item field="login_loginFailedSend"
|
||||
label="登录失败发信"
|
||||
:rules="[{required: true, message: '请选择此项'}]"
|
||||
hide-asterisk>
|
||||
<a-switch v-model="setting.login_loginFailedSend"
|
||||
type="round"
|
||||
checked-value="true"
|
||||
unchecked-value="false"
|
||||
checked-text="开启"
|
||||
unchecked-text="关闭" />
|
||||
<template #extra>
|
||||
开启后当登录失败次数达到阈值时将发送站内信
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 凭证有效期 -->
|
||||
<a-form-item field="login_loginSessionTime"
|
||||
label="凭证有效期"
|
||||
:rules="[{required: true, message: '请输入凭证有效期'}]"
|
||||
hide-asterisk>
|
||||
<a-input-number v-model="setting.login_loginSessionTime"
|
||||
class="input-wrapper"
|
||||
:min="1"
|
||||
:max="99999"
|
||||
placeholder="请输入凭证有效期"
|
||||
allow-clear
|
||||
hide-button>
|
||||
<template #suffix>
|
||||
分
|
||||
</template>
|
||||
</a-input-number>
|
||||
<template #extra>
|
||||
设置登录凭证有效期时长(分)
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 凭证续签间隔 -->
|
||||
<a-form-item field="login_refreshInterval"
|
||||
label="凭证续签间隔"
|
||||
:rules="[{required: true, message: '请输入凭证续签间隔时间'}]"
|
||||
hide-asterisk>
|
||||
<a-input-number v-model="setting.login_refreshInterval"
|
||||
class="input-wrapper"
|
||||
:min="1"
|
||||
:max="99999"
|
||||
placeholder="请输入凭证续签间隔时间"
|
||||
allow-clear
|
||||
hide-button>
|
||||
<template #suffix>
|
||||
分
|
||||
</template>
|
||||
</a-input-number>
|
||||
<template #extra>
|
||||
当登录凭证过期但未超过续签间隔时,系统会自动续签
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 凭证续签最大次数 -->
|
||||
<a-form-item field="login_maxRefreshCount"
|
||||
label="凭证续签最大次数"
|
||||
:rules="[{required: true, message: '请输入凭证续签最大次数'}]"
|
||||
hide-asterisk>
|
||||
<a-input-number v-model="setting.login_maxRefreshCount"
|
||||
class="input-wrapper"
|
||||
:min="0"
|
||||
:max="99999"
|
||||
placeholder="请输入凭证续签最大次数"
|
||||
allow-clear
|
||||
hide-button />
|
||||
<template #extra>
|
||||
凭证续签的最大次数
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 登录失败锁定阈值 -->
|
||||
<a-form-item field="login_loginFailedLockThreshold"
|
||||
label="登录失败锁定阈值"
|
||||
:rules="[{required: true, message: '请输入登录失败锁定阈值'}]"
|
||||
hide-asterisk>
|
||||
<a-input-number v-model="setting.login_loginFailedLockThreshold"
|
||||
class="input-wrapper"
|
||||
:min="0"
|
||||
:max="99999"
|
||||
placeholder="请输入登录失败锁定阈值"
|
||||
allow-clear
|
||||
hide-button />
|
||||
<template #extra>
|
||||
登录失败次数到达该值时账号会自动锁定
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 登录失败锁定时间 -->
|
||||
<a-form-item field="login_loginFailedLockTime"
|
||||
label="登录失败锁定时间"
|
||||
:rules="[{required: true, message: '请输入登录失败锁定时间'}]"
|
||||
hide-asterisk>
|
||||
<a-input-number v-model="setting.login_loginFailedLockTime"
|
||||
class="input-wrapper"
|
||||
:min="0"
|
||||
:max="999999"
|
||||
placeholder="请输入登录失败锁定时间"
|
||||
allow-clear
|
||||
hide-button>
|
||||
<template #suffix>
|
||||
分
|
||||
</template>
|
||||
</a-input-number>
|
||||
<template #extra>
|
||||
登录失败多次后账号锁定的时间(分)
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 登录失败发信阈值 -->
|
||||
<a-form-item field="login_loginFailedSendThreshold"
|
||||
label="登录失败发信阈值"
|
||||
:rules="[{required: true, message: '请输入登录失败发信阈值'}]"
|
||||
hide-asterisk>
|
||||
<a-input-number v-model="setting.login_loginFailedSendThreshold"
|
||||
class="input-wrapper"
|
||||
:min="0"
|
||||
:max="99999"
|
||||
placeholder="请输入登录失败发信阈值"
|
||||
allow-clear
|
||||
hide-button />
|
||||
<template #extra>
|
||||
登录失败次数到达该值时系统将发送站内信
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 按钮 -->
|
||||
<a-form-item v-permission="['infra:system-setting:update']">
|
||||
<!-- 保存 -->
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
@click="save">
|
||||
保存
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'loginSetting',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { LoginSetting } from '@/api/system/setting';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { getSystemSetting, updateSystemSettingBatch } from '@/api/system/setting';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { toAnonymousNumber } from '@/utils';
|
||||
import { SystemSettingTypes } from '../types/const';
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
|
||||
const formRef = ref();
|
||||
const setting = ref<LoginSetting>({} as LoginSetting);
|
||||
|
||||
// 保存
|
||||
const save = async () => {
|
||||
// 验证参数
|
||||
const error = await formRef.value.validate();
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
await updateSystemSettingBatch({
|
||||
type: SystemSettingTypes.LOGIN,
|
||||
settings: setting.value
|
||||
});
|
||||
Message.success('修改成功');
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载配置
|
||||
onMounted(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await getSystemSetting(SystemSettingTypes.LOGIN);
|
||||
setting.value = {
|
||||
...data,
|
||||
login_loginSessionTime: toAnonymousNumber(data.login_loginSessionTime),
|
||||
login_maxRefreshCount: toAnonymousNumber(data.login_maxRefreshCount),
|
||||
login_refreshInterval: toAnonymousNumber(data.login_refreshInterval),
|
||||
login_loginFailedLockThreshold: toAnonymousNumber(data.login_loginFailedLockThreshold),
|
||||
login_loginFailedLockTime: toAnonymousNumber(data.login_loginFailedLockTime),
|
||||
login_loginFailedSendThreshold: toAnonymousNumber(data.login_loginFailedSendThreshold),
|
||||
};
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.input-wrapper {
|
||||
width: 368px;
|
||||
}
|
||||
</style>
|
||||
@@ -8,12 +8,40 @@
|
||||
class="setting-form"
|
||||
label-align="right"
|
||||
:auto-label-width="true">
|
||||
<!-- 重复文件备份 -->
|
||||
<a-form-item field="sftp_uploadPresentBackup"
|
||||
label="重复文件备份"
|
||||
:rules="[{required: true, message: '请选择此项'}]"
|
||||
hide-asterisk>
|
||||
<a-switch v-model="setting.sftp_uploadPresentBackup"
|
||||
type="round"
|
||||
checked-value="true"
|
||||
unchecked-value="false"
|
||||
checked-text="备份"
|
||||
unchecked-text="覆盖" />
|
||||
<template #extra>
|
||||
文件上传时, 若文件存在是否备份原始文件
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 备份文件名称 -->
|
||||
<a-form-item field="sftp_uploadBackupFileName"
|
||||
label="备份文件名称"
|
||||
:rules="[{required: true, message: '请输入备份文件名称'}]"
|
||||
hide-asterisk>
|
||||
<a-input v-model="setting.sftp_uploadBackupFileName"
|
||||
class="input-wrapper"
|
||||
placeholder="请输入备份文件名称模板"
|
||||
allow-clear />
|
||||
<template #extra>
|
||||
${fileName} 文件名称, ${timestamp} 时间戳, ${time} 时间
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 文件预览大小 -->
|
||||
<a-form-item field="previewSize"
|
||||
<a-form-item field="sftp_previewSize"
|
||||
label="文件预览大小"
|
||||
:rules="[{required: true, message: '请输入文件预览大小'}]"
|
||||
hide-asterisk>
|
||||
<a-input-number v-model="setting.previewSize"
|
||||
<a-input-number v-model="setting.sftp_previewSize"
|
||||
class="input-wrapper"
|
||||
:min="0"
|
||||
:max="200"
|
||||
@@ -24,6 +52,9 @@
|
||||
MB
|
||||
</template>
|
||||
</a-input-number>
|
||||
<template #extra>
|
||||
可以直接查看或编辑小于等于该大小的普通文件
|
||||
</template>
|
||||
</a-form-item>
|
||||
<!-- 按钮 -->
|
||||
<a-form-item v-permission="['infra:system-setting:update']">
|
||||
@@ -50,6 +81,7 @@
|
||||
import { getSystemSetting, updateSystemSettingBatch } from '@/api/system/setting';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { toAnonymousNumber } from '@/utils';
|
||||
import { SystemSettingTypes } from '../types/const';
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
@@ -68,7 +100,7 @@
|
||||
try {
|
||||
await updateSystemSettingBatch({
|
||||
type: SystemSettingTypes.SFTP,
|
||||
settings: setting.value
|
||||
settings: setting.value,
|
||||
});
|
||||
Message.success('修改成功');
|
||||
} catch (e) {
|
||||
@@ -81,8 +113,11 @@
|
||||
onMounted(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await getSystemSetting<SftpSetting>(SystemSettingTypes.SFTP);
|
||||
setting.value = data;
|
||||
const { data } = await getSystemSetting(SystemSettingTypes.SFTP);
|
||||
setting.value = {
|
||||
...data,
|
||||
sftp_previewSize: toAnonymousNumber(data.sftp_previewSize),
|
||||
};
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -93,6 +128,6 @@
|
||||
|
||||
<style lang="less" scoped>
|
||||
.input-wrapper {
|
||||
width: 328px;
|
||||
width: 368px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,12 +11,30 @@
|
||||
title="SFTP">
|
||||
<sftp-setting />
|
||||
</a-tab-pane>
|
||||
<!-- 登录设置 -->
|
||||
<a-tab-pane v-permission="['infra:system-setting:update']"
|
||||
key="login"
|
||||
title="登录设置">
|
||||
<login-setting />
|
||||
</a-tab-pane>
|
||||
<!-- 加密设置 -->
|
||||
<a-tab-pane v-permission="['infra:system-setting:update']"
|
||||
key="encrypt"
|
||||
title="加密设置">
|
||||
<encrypt-setting />
|
||||
</a-tab-pane>
|
||||
<!-- 日志设置 -->
|
||||
<a-tab-pane v-permission="['infra:system-setting:update']"
|
||||
key="log"
|
||||
title="日志设置">
|
||||
<log-setting />
|
||||
</a-tab-pane>
|
||||
<!-- 自动清理 -->
|
||||
<a-tab-pane v-permission="['infra:system-setting:update']"
|
||||
key="auto-clear"
|
||||
title="自动清理">
|
||||
<auto-clear-setting />
|
||||
</a-tab-pane>
|
||||
<!-- 关于 -->
|
||||
<a-tab-pane key="about" title="关于">
|
||||
<about-setting />
|
||||
@@ -36,7 +54,10 @@
|
||||
import { useRoute } from 'vue-router';
|
||||
import usePermission from '@/hooks/permission';
|
||||
import SftpSetting from './components/sftp-setting.vue';
|
||||
import LoginSetting from './components/login-setting.vue';
|
||||
import EncryptSetting from './components/encrypt-setting.vue';
|
||||
import LogSetting from './components/log-setting.vue';
|
||||
import AutoClearSetting from './components/auto-clear-setting.vue';
|
||||
import AboutSetting from './components/about-setting.vue';
|
||||
|
||||
const route = useRoute();
|
||||
@@ -96,6 +117,7 @@
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 600;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.setting-form {
|
||||
@@ -104,6 +126,7 @@
|
||||
|
||||
.arco-descriptions-title {
|
||||
font-weight: 600;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.alert-href {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { SystemSettingType } from '@/api/system/setting';
|
||||
|
||||
// 系统设置类型
|
||||
export const SystemSettingTypes: Record<SystemSettingType, SystemSettingType> = {
|
||||
SFTP: 'SFTP',
|
||||
ENCRYPT: 'ENCRYPT',
|
||||
export const SystemSettingTypes = {
|
||||
SFTP: 'sftp',
|
||||
ENCRYPT: 'encrypt',
|
||||
LOGIN: 'login',
|
||||
LOG: 'log',
|
||||
AUTO_CLEAR: 'autoClear',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user