Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
032f1763f6 | ||
|
|
d071ef64d8 | ||
|
|
c820443a3b | ||
|
|
14c4e77445 | ||
|
|
79d9f69ed4 | ||
|
|
6c9065072d | ||
|
|
05bc6c1fbb | ||
|
|
a1dd9eec01 | ||
|
|
660df7c110 | ||
|
|
093501a400 | ||
|
|
7943deb924 | ||
|
|
490167e649 | ||
|
|
8635f6bb05 | ||
|
|
ac46dd6703 |
@@ -1,5 +1,5 @@
|
||||
#/bin/bash
|
||||
version=2.3.4
|
||||
version=2.3.5
|
||||
docker build -t orion-visor-adminer:${version} .
|
||||
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:${version}
|
||||
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:latest
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#/bin/bash
|
||||
version=2.3.4
|
||||
version=2.3.5
|
||||
cp -r ../../sql ./sql
|
||||
docker build -t orion-visor-mysql:${version} .
|
||||
rm -rf ./sql
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#/bin/bash
|
||||
version=2.3.4
|
||||
version=2.3.5
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:${version}
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:${version}
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:${version}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#/bin/bash
|
||||
version=2.3.4
|
||||
version=2.3.5
|
||||
docker build -t orion-visor-redis:${version} .
|
||||
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:${version}
|
||||
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:latest
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#/bin/bash
|
||||
version=2.3.4
|
||||
version=2.3.5
|
||||
mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar
|
||||
mv ../../orion-visor-ui/dist ./dist
|
||||
docker build -t orion-visor-service:${version} .
|
||||
|
||||
@@ -20,21 +20,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.launch.configuration;
|
||||
package org.dromara.visor.common.configuration;
|
||||
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 应用配置类
|
||||
* spring 配置类
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2023/6/20 10:34
|
||||
*/
|
||||
@Configuration
|
||||
public class LaunchApplicationConfiguration {
|
||||
public class SpringConfiguration {
|
||||
|
||||
/**
|
||||
* @return spring 容器工具类
|
||||
@@ -36,7 +36,7 @@ public interface AppConst extends OrionConst {
|
||||
/**
|
||||
* 同 ${orion.version} 迭代时候需要手动更改
|
||||
*/
|
||||
String VERSION = "2.3.4";
|
||||
String VERSION = "2.3.5";
|
||||
|
||||
/**
|
||||
* 同 ${spring.application.name}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<url>https://github.com/dromara/orion-visor</url>
|
||||
|
||||
<properties>
|
||||
<revision>2.3.4</revision>
|
||||
<revision>2.3.5</revision>
|
||||
<spring.boot.version>2.7.17</spring.boot.version>
|
||||
<spring.boot.admin.version>2.7.15</spring.boot.admin.version>
|
||||
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
package org.dromara.visor.framework.mybatis.core.generator;
|
||||
|
||||
import cn.orionsec.kit.lang.constant.Const;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.Systems;
|
||||
import cn.orionsec.kit.lang.utils.ansi.AnsiAppender;
|
||||
import cn.orionsec.kit.lang.utils.ansi.style.AnsiFont;
|
||||
import cn.orionsec.kit.lang.utils.ansi.style.color.AnsiForeground;
|
||||
@@ -32,6 +34,8 @@ import org.dromara.visor.framework.mybatis.core.generator.template.Table;
|
||||
import org.dromara.visor.framework.mybatis.core.generator.template.Template;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 代码生成器
|
||||
@@ -42,6 +46,8 @@ import java.io.File;
|
||||
*/
|
||||
public class CodeGenerators {
|
||||
|
||||
private static final Pattern ENV_VAR_PATTERN = Pattern.compile("\\$\\{([^:]+):([^}]+)\\}");
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 输出路径
|
||||
String outputDir = "D:/MP/";
|
||||
@@ -76,11 +82,6 @@ public class CodeGenerators {
|
||||
.disableUnitTest()
|
||||
.enableProviderApi()
|
||||
.vue("system", "message")
|
||||
.dict("messageClassify", "classify", "messageClassify")
|
||||
.comment("消息分类")
|
||||
.fields("NOTICE", "TODO")
|
||||
.labels("通知", "待办")
|
||||
.valueUseFields()
|
||||
.dict("messageType", "type", "messageType")
|
||||
.comment("消息类型")
|
||||
.fields("EXEC_FAILED", "UPLOAD_FAILED")
|
||||
@@ -94,9 +95,9 @@ public class CodeGenerators {
|
||||
// jdbc 配置 - 使用配置文件
|
||||
File yamlFile = new File("orion-visor-launch/src/main/resources/application-dev.yaml");
|
||||
YmlExt yaml = YmlExt.load(yamlFile);
|
||||
String url = yaml.getValue("spring.datasource.druid.url");
|
||||
String username = yaml.getValue("spring.datasource.druid.username");
|
||||
String password = yaml.getValue("spring.datasource.druid.password");
|
||||
String url = resolveConfigValue(yaml.getValue("spring.datasource.druid.url"));
|
||||
String username = resolveConfigValue(yaml.getValue("spring.datasource.druid.username"));
|
||||
String password = resolveConfigValue(yaml.getValue("spring.datasource.druid.password"));
|
||||
|
||||
// 执行
|
||||
runGenerator(outputDir, author,
|
||||
@@ -147,4 +148,31 @@ public class CodeGenerators {
|
||||
System.out.print(line);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析实际的配置
|
||||
*
|
||||
* @param value value
|
||||
* @return value
|
||||
*/
|
||||
private static String resolveConfigValue(String value) {
|
||||
if (Strings.isBlank(value)) {
|
||||
return value;
|
||||
}
|
||||
Matcher matcher = ENV_VAR_PATTERN.matcher(value);
|
||||
StringBuffer resultString = new StringBuffer();
|
||||
while (matcher.find()) {
|
||||
// 环境变量名
|
||||
String envVar = matcher.group(1);
|
||||
// 默认值
|
||||
String defaultValue = matcher.group(2);
|
||||
// 获取环境变量的值
|
||||
String envValue = Systems.getEnv(envVar, defaultValue);
|
||||
// 替换占位符
|
||||
matcher.appendReplacement(resultString, Matcher.quoteReplacement(envValue));
|
||||
}
|
||||
// 处理结尾的剩余部分
|
||||
matcher.appendTail(resultString);
|
||||
return resultString.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ SELECT @TYPE_KEY_ID:= id FROM dict_key WHERE key_name = 'operatorLogType' AND de
|
||||
INSERT INTO dict_value
|
||||
(`key_id`, `key_name`, `value`, `label`, `extra`, `sort`, `create_time`, `update_time`, `creator`, `updater`, `deleted`)
|
||||
VALUES
|
||||
(@MODULE_KEY_ID, 'operatorLogModule', '${package.ModuleName}:${typeHyphen}', '$!{table.comment}', '{}', @MODULE_KEY_MAX_SORT + 10, now(), now(), '1', '1', 0),
|
||||
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:create', '创建$!{table.comment}', '{}', 10, now(), now(), '1', '1', 0),
|
||||
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:update', '更新$!{table.comment}', '{}', 20, now(), now(), '1', '1', 0),
|
||||
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:delete', '删除$!{table.comment}', '{}', 30, now(), now(), '1', '1', 0);
|
||||
(@MODULE_KEY_ID, 'operatorLogModule', '${package.ModuleName}:${typeHyphen}', '$!{table.comment}', '{}', @MODULE_KEY_MAX_SORT + 10, now(), now(), 'admin', 'admin', 0),
|
||||
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:create', '创建$!{table.comment}', '{}', 10, now(), now(), 'admin', 'admin', 0),
|
||||
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:update', '更新$!{table.comment}', '{}', 20, now(), now(), 'admin', 'admin', 0),
|
||||
(@TYPE_KEY_ID, 'operatorLogType', '${typeHyphen}:delete', '删除$!{table.comment}', '{}', 30, now(), now(), 'admin', 'admin', 0);
|
||||
#end
|
||||
|
||||
#if($dictMap.entrySet().size() > 0)
|
||||
@@ -23,7 +23,7 @@ VALUES
|
||||
INSERT INTO dict_key
|
||||
(`key_name`, `value_type`, `extra_schema`, `description`, `create_time`, `update_time`, `creator`, `updater`, `deleted`)
|
||||
VALUES
|
||||
('$enumEntity.value.keyName', 'STRING', '$enumEntity.value.extraSchema', '$enumEntity.value.comment', now(), now(), '1', '1', 0);
|
||||
('$enumEntity.value.keyName', 'STRING', '$enumEntity.value.extraSchema', '$enumEntity.value.comment', now(), now(), 'admin', 'admin', 0);
|
||||
|
||||
-- 设置临时配置项id
|
||||
SELECT @TMP_KEY_ID:=LAST_INSERT_ID();
|
||||
@@ -35,7 +35,7 @@ VALUES
|
||||
#set($count = $enumEntity.value.fields.size() - 1)
|
||||
#foreach($index in [0..$count])
|
||||
#set($sort = $index * 10 + 10)
|
||||
(@TMP_KEY_ID, '$enumEntity.value.keyName', '$enumEntity.value.values.get($index)', '$enumEntity.value.labels.get($index)', #if($enumEntity.value.extraJson.size() > $index)'$enumEntity.value.extraJson.get($index)'#else'{}'#end, $sort, now(), now(), '1', '1', 0)#if($foreach.hasNext),#else;#end
|
||||
(@TMP_KEY_ID, '$enumEntity.value.keyName', '$enumEntity.value.values.get($index)', '$enumEntity.value.labels.get($index)', #if($enumEntity.value.extraJson.size() > $index)'$enumEntity.value.extraJson.get($index)'#else'{}'#end, $sort, now(), now(), 'admin', 'admin', 0)#if($foreach.hasNext),#else;#end
|
||||
#end
|
||||
|
||||
#end
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
INSERT INTO system_menu
|
||||
(parent_id, name, type, sort, visible, status, cache, component, creator, updater, deleted)
|
||||
VALUES
|
||||
(0, '${table.comment}管理', 1, 10, 1, 1, 1, '${vue.moduleEntityFirstLower}Module', '1', '1', 0);
|
||||
(0, '${table.comment}管理', 1, 10, 1, 1, 1, '${vue.moduleEntityFirstLower}Module', 'admin', 'admin', 0);
|
||||
|
||||
-- 设置临时父菜单id
|
||||
SELECT @TMP_PARENT_ID:=LAST_INSERT_ID();
|
||||
@@ -13,7 +13,7 @@ SELECT @TMP_PARENT_ID:=LAST_INSERT_ID();
|
||||
INSERT INTO system_menu
|
||||
(parent_id, name, type, sort, visible, status, cache, component, creator, updater, deleted)
|
||||
VALUES
|
||||
(@TMP_PARENT_ID, '$table.comment', 2, 10, 1, 1, 1, '$vue.featureEntityFirstLower', '1', '1', 0);
|
||||
(@TMP_PARENT_ID, '$table.comment', 2, 10, 1, 1, 1, '$vue.featureEntityFirstLower', 'admin', 'admin', 0);
|
||||
|
||||
-- 设置临时子菜单id
|
||||
SELECT @TMP_SUB_ID:=LAST_INSERT_ID();
|
||||
@@ -22,7 +22,7 @@ SELECT @TMP_SUB_ID:=LAST_INSERT_ID();
|
||||
INSERT INTO system_menu
|
||||
(parent_id, name, permission, type, sort, creator, updater, deleted)
|
||||
VALUES
|
||||
(@TMP_SUB_ID, '查询$table.comment', '${package.ModuleName}:${typeHyphen}:query', 3, 10, '1', '1', 0),
|
||||
(@TMP_SUB_ID, '创建$table.comment', '${package.ModuleName}:${typeHyphen}:create', 3, 20, '1', '1', 0),
|
||||
(@TMP_SUB_ID, '修改$table.comment', '${package.ModuleName}:${typeHyphen}:update', 3, 30, '1', '1', 0),
|
||||
(@TMP_SUB_ID, '删除$table.comment', '${package.ModuleName}:${typeHyphen}:delete', 3, 40, '1', '1', 0);
|
||||
(@TMP_SUB_ID, '查询$table.comment', '${package.ModuleName}:${typeHyphen}:query', 3, 10, 'admin', 'admin', 0),
|
||||
(@TMP_SUB_ID, '创建$table.comment', '${package.ModuleName}:${typeHyphen}:create', 3, 20, 'admin', 'admin', 0),
|
||||
(@TMP_SUB_ID, '修改$table.comment', '${package.ModuleName}:${typeHyphen}:update', 3, 30, 'admin', 'admin', 0),
|
||||
(@TMP_SUB_ID, '删除$table.comment', '${package.ModuleName}:${typeHyphen}:delete', 3, 40, 'admin', 'admin', 0);
|
||||
|
||||
@@ -8,7 +8,7 @@ const $vue.moduleConst: AppRouteRecordRaw = {
|
||||
children: [
|
||||
{
|
||||
name: '$vue.featureEntityFirstLower',
|
||||
path: '/$vue.feature',
|
||||
path: '/$vue.module/$vue.feature',
|
||||
component: () => import('@/views/$vue.module/$vue.feature/index.vue'),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -3,7 +3,7 @@ import { dateFormat } from '@/utils';
|
||||
|
||||
const fieldConfig = {
|
||||
rowGap: '10px',
|
||||
labelSpan: 8,
|
||||
labelSpan: 6,
|
||||
minHeight: '22px',
|
||||
fields: [
|
||||
{
|
||||
|
||||
@@ -38,7 +38,27 @@ public class StaticResourceAuthorizeRequestsCustomizer extends AuthorizeRequests
|
||||
@Override
|
||||
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
|
||||
// 静态资源可匿名访问
|
||||
registry.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll();
|
||||
registry.antMatchers(HttpMethod.GET,
|
||||
"/*.html",
|
||||
"/*.css",
|
||||
"/*.js",
|
||||
"/*.gz",
|
||||
"/*.ico",
|
||||
"/*.jpg",
|
||||
"/*.png",
|
||||
"/*.svg",
|
||||
"/*.json",
|
||||
"/*.webmanifest",
|
||||
"/**/*.html",
|
||||
"/**/*.css",
|
||||
"/**/*.js",
|
||||
"/**/*.gz",
|
||||
"/**/*.ico",
|
||||
"/**/*.jpg",
|
||||
"/**/*.png",
|
||||
"/**/*.svg",
|
||||
"/**/*.json"
|
||||
).permitAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ package org.dromara.visor.framework.test.core.base;
|
||||
|
||||
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
|
||||
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
|
||||
import org.dromara.visor.common.configuration.SpringConfiguration;
|
||||
import org.dromara.visor.framework.datasource.configuration.OrionDataSourceAutoConfiguration;
|
||||
import org.dromara.visor.framework.mybatis.configuration.OrionMybatisAutoConfiguration;
|
||||
import org.dromara.visor.framework.redis.configuration.OrionRedisAutoConfiguration;
|
||||
@@ -57,6 +58,8 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class BaseUnitTest {
|
||||
|
||||
@Import({
|
||||
// spring
|
||||
SpringConfiguration.class,
|
||||
// mock
|
||||
OrionMockBeanTestConfiguration.class,
|
||||
OrionMockRedisTestConfiguration.class,
|
||||
@@ -74,7 +77,6 @@ public class BaseUnitTest {
|
||||
RedisAutoConfiguration.class,
|
||||
RedissonAutoConfiguration.class,
|
||||
})
|
||||
// TODO
|
||||
public static class Application {
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,11 @@ import java.util.Optional;
|
||||
* @version 1.0.0
|
||||
* @since 2023/6/19 16:55
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"org.dromara.visor.launch", "org.dromara.visor.module"})
|
||||
@SpringBootApplication(scanBasePackages = {
|
||||
"org.dromara.visor.launch",
|
||||
"org.dromara.visor.common",
|
||||
"org.dromara.visor.module"
|
||||
})
|
||||
public class LaunchApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -39,9 +39,9 @@ import java.util.function.Function;
|
||||
*/
|
||||
public class ReplaceVersion {
|
||||
|
||||
private static final String TARGET_VERSION = "2.3.3";
|
||||
private static final String TARGET_VERSION = "2.3.4";
|
||||
|
||||
private static final String REPLACE_VERSION = "2.3.4";
|
||||
private static final String REPLACE_VERSION = "2.3.5";
|
||||
|
||||
private static final String PATH = new File("").getAbsolutePath();
|
||||
|
||||
|
||||
@@ -82,6 +82,10 @@
|
||||
<groupId>org.dromara.visor</groupId>
|
||||
<artifactId>orion-visor-spring-boot-starter-job</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dromara.visor</groupId>
|
||||
<artifactId>orion-visor-spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -63,7 +63,7 @@ public class AssetAuthorizedDataServiceController {
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@GetMapping("/current-host")
|
||||
@Operation(summary = "查询当前用户已授权的主机")
|
||||
public AuthorizedHostWrapperVO getCurrentAuthorizedHost(@RequestParam("type") String type) {
|
||||
public AuthorizedHostWrapperVO getCurrentAuthorizedHost(@RequestParam(value = "type", required = false) String type) {
|
||||
return assetAuthorizedDataService.getUserAuthorizedHost(SecurityUtils.getLoginUserId(), type);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,17 @@ public interface TerminalConnectLogDAO extends IMapper<TerminalConnectLogDO> {
|
||||
* @param limit limit
|
||||
* @return hostId
|
||||
*/
|
||||
List<Long> selectLatestConnectHostId(@Param("userId") Long userId, @Param("type") String type, @Param("limit") Integer limit);
|
||||
default List<Long> selectLatestConnectHostId(Long userId, String type, Integer limit) {
|
||||
return this.of()
|
||||
.createWrapper(true)
|
||||
.select(TerminalConnectLogDO::getHostId)
|
||||
.eq(TerminalConnectLogDO::getUserId, userId)
|
||||
.eq(TerminalConnectLogDO::getType, type)
|
||||
.orderByDesc(TerminalConnectLogDO::getId)
|
||||
.then()
|
||||
.limit(limit)
|
||||
.list(TerminalConnectLogDO::getHostId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询终端连接日志用户数量
|
||||
|
||||
@@ -31,10 +31,10 @@ package org.dromara.visor.module.asset.handler.host.jsch;
|
||||
*/
|
||||
public interface SessionMessage {
|
||||
|
||||
String AUTHENTICATION_FAILURE = "authentication failed. please check the configuration. - {}";
|
||||
String AUTHENTICATION_FAILURE = "身份认证失败. {}";
|
||||
|
||||
String SERVER_UNREACHABLE = "remote server unreachable. please check the configuration. - {}";
|
||||
String SERVER_UNREACHABLE = "无法连接至服务器. {}";
|
||||
|
||||
String CONNECTION_TIMEOUT = "connection timeout. - {}";
|
||||
String CONNECTION_TIMEOUT = "连接服务器超时. {}";
|
||||
|
||||
}
|
||||
|
||||
@@ -135,13 +135,6 @@ public interface HostService {
|
||||
*/
|
||||
void deleteHostRelByIdListAsync(List<Long> idList);
|
||||
|
||||
/**
|
||||
* 获取当前更新配置的 hostId
|
||||
*
|
||||
* @return hostId
|
||||
*/
|
||||
Long getCurrentUpdateConfigHostId();
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
*/
|
||||
|
||||
@@ -83,8 +83,6 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class HostServiceImpl implements HostService {
|
||||
|
||||
private static final ThreadLocal<Long> CURRENT_UPDATE_CONFIG_ID = new ThreadLocal<>();
|
||||
|
||||
@Resource
|
||||
private HostDAO hostDAO;
|
||||
|
||||
@@ -185,30 +183,25 @@ public class HostServiceImpl implements HostService {
|
||||
OperatorLogs.add(ExtraFieldConst.CONFIG, param);
|
||||
log.info("HostService-updateHostConfig request: {}", param);
|
||||
Long id = request.getId();
|
||||
try {
|
||||
CURRENT_UPDATE_CONFIG_ID.set(id);
|
||||
// 查询主机信息
|
||||
HostDO host = hostDAO.selectById(id);
|
||||
Valid.notNull(host, ErrorMessage.HOST_ABSENT);
|
||||
HostTypeEnum type = Valid.valid(HostTypeEnum::of, host.getType());
|
||||
GenericsDataModel beforeConfig = type.parse(host.getConfig());
|
||||
GenericsDataModel newConfig = type.parse(request.getConfig());
|
||||
// 添加日志参数
|
||||
OperatorLogs.add(OperatorLogs.ID, id);
|
||||
OperatorLogs.add(OperatorLogs.NAME, host.getName());
|
||||
// 更新前校验
|
||||
type.doValid(beforeConfig, newConfig);
|
||||
// 修改配置
|
||||
HostDO updateHost = HostDO.builder()
|
||||
.id(id)
|
||||
.config(newConfig.serial())
|
||||
.build();
|
||||
int effect = hostDAO.updateById(updateHost);
|
||||
log.info("HostService-updateHostConfig effect: {}", effect);
|
||||
return effect;
|
||||
} finally {
|
||||
CURRENT_UPDATE_CONFIG_ID.remove();
|
||||
}
|
||||
// 查询主机信息
|
||||
HostDO host = hostDAO.selectById(id);
|
||||
Valid.notNull(host, ErrorMessage.HOST_ABSENT);
|
||||
HostTypeEnum type = Valid.valid(HostTypeEnum::of, host.getType());
|
||||
GenericsDataModel beforeConfig = type.parse(host.getConfig());
|
||||
GenericsDataModel newConfig = type.parse(request.getConfig());
|
||||
// 添加日志参数
|
||||
OperatorLogs.add(OperatorLogs.ID, id);
|
||||
OperatorLogs.add(OperatorLogs.NAME, host.getName());
|
||||
// 更新前校验
|
||||
type.doValid(beforeConfig, newConfig);
|
||||
// 修改配置
|
||||
HostDO updateHost = HostDO.builder()
|
||||
.id(id)
|
||||
.config(newConfig.serial())
|
||||
.build();
|
||||
int effect = hostDAO.updateById(updateHost);
|
||||
log.info("HostService-updateHostConfig effect: {}", effect);
|
||||
return effect;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -348,11 +341,6 @@ public class HostServiceImpl implements HostService {
|
||||
dataExtraApi.deleteByRelIdList(DataExtraTypeEnum.HOST, idList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCurrentUpdateConfigHostId() {
|
||||
return CURRENT_UPDATE_CONFIG_ID.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCache() {
|
||||
RedisMaps.scanKeysDelete(HostCacheKeyDefine.HOST_INFO.format("*"));
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.dromara.visor.module.asset.enums.HostIdentityTypeEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostSshAuthTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.config.model.HostSshConfigModel;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.model.HostSshExtraModel;
|
||||
import org.dromara.visor.module.asset.service.AssetAuthorizedDataService;
|
||||
import org.dromara.visor.module.asset.service.HostConfigService;
|
||||
import org.dromara.visor.module.asset.service.HostExtraService;
|
||||
import org.dromara.visor.module.asset.service.TerminalService;
|
||||
@@ -81,7 +82,7 @@ public class TerminalServiceImpl implements TerminalService {
|
||||
private HostExtraService hostExtraService;
|
||||
|
||||
@Resource
|
||||
private AssetAuthorizedDataServiceImpl assetAuthorizedDataService;
|
||||
private AssetAuthorizedDataService assetAuthorizedDataService;
|
||||
|
||||
@Resource
|
||||
private HostDAO hostDAO;
|
||||
|
||||
@@ -33,16 +33,6 @@
|
||||
id, user_id, username, host_id, host_name, host_address, type, session_id, status, start_time, end_time, extra_info, create_time, update_time, deleted
|
||||
</sql>
|
||||
|
||||
<select id="selectLatestConnectHostId" resultType="java.lang.Long">
|
||||
SELECT host_id
|
||||
FROM terminal_connect_log
|
||||
WHERE deleted = 0
|
||||
AND type = #{type}
|
||||
AND user_id = #{userId}
|
||||
ORDER BY id DESC
|
||||
LIMIT #{limit}
|
||||
</select>
|
||||
|
||||
<select id="selectConnectLogUserCount" resultMap="CountResultMap">
|
||||
SELECT DATE(create_time) connect_date, COUNT(1) total_count
|
||||
FROM terminal_connect_log
|
||||
|
||||
@@ -3,4 +3,4 @@ VITE_API_BASE_URL=http://127.0.0.1:9200/orion-visor/api
|
||||
# websocket 路径
|
||||
VITE_WS_BASE_URL=ws://127.0.0.1:9200/orion-visor/keep-alive
|
||||
# 版本号
|
||||
VITE_APP_VERSION=2.3.4
|
||||
VITE_APP_VERSION=2.3.5
|
||||
|
||||
@@ -3,4 +3,4 @@ VITE_API_BASE_URL=/orion-visor/api
|
||||
# websocket 路径
|
||||
VITE_WS_BASE_URL=/orion-visor/keep-alive
|
||||
# 版本号
|
||||
VITE_APP_VERSION=2.3.4
|
||||
VITE_APP_VERSION=2.3.5
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "orion-visor-ui",
|
||||
"description": "Orion Visor UI",
|
||||
"version": "2.3.4",
|
||||
"version": "2.3.5",
|
||||
"private": true,
|
||||
"author": "Jiahang Li",
|
||||
"license": "Apache 2.0",
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
v-model:selected-group="selectedGroup"
|
||||
:host-list="hostList"
|
||||
:groups="hosts?.groupTree as any"
|
||||
:nodes="treeNodes" />
|
||||
:nodes="treeNodes as any" />
|
||||
<!-- 列表视图 -->
|
||||
<host-table v-else
|
||||
v-model:selected-keys="selectedKeys"
|
||||
@@ -83,13 +83,13 @@
|
||||
import { onMounted, ref, watch, computed } from 'vue';
|
||||
import { dataColor } from '@/utils';
|
||||
import { dictKeys, NewConnectionType, newConnectionTypeKey } from './types/const';
|
||||
import { useDictStore } from '@/store';
|
||||
import { useCacheStore, useDictStore } from '@/store';
|
||||
import { tagLabelFilter } from '@/types/form';
|
||||
import { tagColor } from '@/views/asset/host-list/types/const';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import { getCurrentAuthorizedHost } from '@/api/asset/asset-authorized-data';
|
||||
import { getAuthorizedHostOptions } from '@/types/options';
|
||||
import { getLatestConnectHostId } from '@/api/asset/terminal-connect-log';
|
||||
import HostTable from './components/host-table.vue';
|
||||
import HostGroup from './components/host-group.vue';
|
||||
|
||||
@@ -151,10 +151,13 @@
|
||||
setLoading(true);
|
||||
try {
|
||||
// 加载主机列表
|
||||
const { data } = await getCurrentAuthorizedHost(props.type);
|
||||
hosts.value = data;
|
||||
const data = await useCacheStore().loadAuthorizedHosts(props.type);
|
||||
// 禁用别名
|
||||
data.hostList.forEach(s => s.alias = undefined as unknown as string);
|
||||
// 查询最近连接的主机
|
||||
const { data: latestHosts } = await getLatestConnectHostId(props.type as string, 30);
|
||||
data.latestHosts = latestHosts;
|
||||
hosts.value = data;
|
||||
// 设置主机搜索选项
|
||||
filterOptions.value = getAuthorizedHostOptions(data.hostList);
|
||||
} catch (e) {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ExecLogQueryResponse } from '@/api/exec/exec-log';
|
||||
import type { ExecType, ILogAppender } from '../const';
|
||||
import { onUnmounted, ref, nextTick, onMounted } from 'vue';
|
||||
import { onUnmounted, ref, nextTick, onMounted, markRaw } from 'vue';
|
||||
import { getExecCommandLogStatus } from '@/api/exec/exec-command-log';
|
||||
import { getExecJobLogStatus } from '@/api/exec/exec-job-log';
|
||||
import { dictKeys, ExecHostStatus, ExecStatus } from '../const';
|
||||
@@ -58,11 +58,11 @@
|
||||
const { log_webScrollLines } = await useCacheStore().loadSystemSetting();
|
||||
const scrollLines = toAnonymousNumber(log_webScrollLines) || 1000;
|
||||
// 创建 appender
|
||||
appender.value = new LogAppender({
|
||||
appender.value = markRaw(new LogAppender({
|
||||
id: record.id,
|
||||
type: props.type,
|
||||
scrollLines,
|
||||
});
|
||||
}));
|
||||
// 定时查询执行状态
|
||||
if (record.status === ExecStatus.WAITING ||
|
||||
record.status === ExecStatus.RUNNING) {
|
||||
|
||||
@@ -8,17 +8,17 @@ const ASSET_AUDIT: AppRouteRecordRaw = {
|
||||
children: [
|
||||
{
|
||||
name: 'connectLog',
|
||||
path: '/connect-log',
|
||||
path: '/audit/connect-log',
|
||||
component: () => import('@/views/asset-audit/connect-log/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'connectSession',
|
||||
path: '/connect-session',
|
||||
path: '/audit/connect-session',
|
||||
component: () => import('@/views/asset-audit/connect-session/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'sftpLog',
|
||||
path: '/sftp-log',
|
||||
path: '/audit/sftp-log',
|
||||
component: () => import('@/views/asset-audit/sftp-log/index.vue'),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -8,19 +8,19 @@ const ASSET: AppRouteRecordRaw = {
|
||||
children: [
|
||||
{
|
||||
name: 'hostList',
|
||||
path: '/host-list',
|
||||
path: '/asset/host',
|
||||
component: () => import('@/views/asset/host-list/index.vue'),
|
||||
}, {
|
||||
name: 'hostKey',
|
||||
path: '/host-key',
|
||||
path: '/asset/host-key',
|
||||
component: () => import('@/views/asset/host-key/index.vue'),
|
||||
}, {
|
||||
name: 'hostIdentity',
|
||||
path: '/host-identity',
|
||||
path: '/asset/host-identity',
|
||||
component: () => import('@/views/asset/host-identity/index.vue'),
|
||||
}, {
|
||||
name: 'assetGrant',
|
||||
path: '/asset-grant',
|
||||
path: '/asset/grant',
|
||||
component: () => import('@/views/asset/grant/index.vue'),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -9,37 +9,37 @@ const EXEC: Array<AppRouteRecordRaw> = [
|
||||
children: [
|
||||
{
|
||||
name: 'execCommand',
|
||||
path: '/exec-command',
|
||||
path: '/exec/command',
|
||||
component: () => import('@/views/exec/exec-command/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'execCommandLog',
|
||||
path: '/exec-log',
|
||||
path: '/exec/command-log',
|
||||
component: () => import('@/views/exec/exec-command-log/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'execJob',
|
||||
path: '/exec-job',
|
||||
path: '/exec/job',
|
||||
component: () => import('@/views/exec/exec-job/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'execJobLog',
|
||||
path: '/exec-job-log',
|
||||
path: '/exec/job-log',
|
||||
component: () => import('@/views/exec/exec-job-log/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'batchUpload',
|
||||
path: '/batch-upload',
|
||||
path: '/exec/upload',
|
||||
component: () => import('@/views/exec/batch-upload/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'uploadTask',
|
||||
path: '/upload-task',
|
||||
path: '/exec/upload-task',
|
||||
component: () => import('@/views/exec/upload-task/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'execTemplate',
|
||||
path: '/exec-template',
|
||||
path: '/exec/template',
|
||||
component: () => import('@/views/exec/exec-template/index.vue'),
|
||||
},
|
||||
],
|
||||
@@ -50,7 +50,7 @@ const EXEC: Array<AppRouteRecordRaw> = [
|
||||
children: [
|
||||
{
|
||||
name: 'execJobLogView',
|
||||
path: '/job-log-view',
|
||||
path: '/exec/job-log/view',
|
||||
component: () => import('@/views/exec/exec-job-log-view/index.vue'),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -8,17 +8,17 @@ const SYSTEM: AppRouteRecordRaw = {
|
||||
children: [
|
||||
{
|
||||
name: 'systemMenu',
|
||||
path: '/menu',
|
||||
path: '/system/menu',
|
||||
component: () => import('@/views/system/menu/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'dictKey',
|
||||
path: '/dict-key',
|
||||
path: '/system/dict-key',
|
||||
component: () => import('@/views/system/dict-key/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'dictValue',
|
||||
path: '/dict-value',
|
||||
path: '/system/dict-value',
|
||||
component: () => import('@/views/system/dict-value/index.vue'),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,22 +8,22 @@ const USER: AppRouteRecordRaw = {
|
||||
children: [
|
||||
{
|
||||
name: 'role',
|
||||
path: '/role',
|
||||
path: '/user/role',
|
||||
component: () => import('@/views/user/role/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'user',
|
||||
path: '/user',
|
||||
path: '/user/list',
|
||||
component: () => import('@/views/user/user/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'userInfo',
|
||||
path: '/user-info',
|
||||
path: '/user/info',
|
||||
component: () => import('@/views/user/info/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'operatorLog',
|
||||
path: '/operator-log',
|
||||
path: '/user/operator-log',
|
||||
component: () => import('@/views/user/operator-log/index.vue'),
|
||||
},
|
||||
],
|
||||
|
||||
11
orion-visor-ui/src/store/modules/cache/index.ts
vendored
11
orion-visor-ui/src/store/modules/cache/index.ts
vendored
@@ -15,7 +15,7 @@ import { getHostKeyList } from '@/api/asset/host-key';
|
||||
import { getHostIdentityList } from '@/api/asset/host-identity';
|
||||
import { getHostGroupTree } from '@/api/asset/host-group';
|
||||
import { getMenuList } from '@/api/system/menu';
|
||||
import { getCurrentAuthorizedHostIdentity, getCurrentAuthorizedHostKey } from '@/api/asset/asset-authorized-data';
|
||||
import { getCurrentAuthorizedHost, getCurrentAuthorizedHostIdentity, getCurrentAuthorizedHostKey } from '@/api/asset/asset-authorized-data';
|
||||
import { getCommandSnippetGroupList } from '@/api/asset/command-snippet-group';
|
||||
import { getExecJobList } from '@/api/exec/exec-job';
|
||||
import { getPathBookmarkGroupList } from '@/api/asset/path-bookmark-group';
|
||||
@@ -99,8 +99,8 @@ export default defineStore('cache', {
|
||||
},
|
||||
|
||||
// 获取主机列表
|
||||
async loadHosts(type: HostType, force = false) {
|
||||
return await this.load(`host_${type}`, () => getHostList(type), ['asset:host:query'], force);
|
||||
async loadHosts(type: HostType = '', force = false) {
|
||||
return await this.load(`host_${type || 'ALL'}`, () => getHostList(type), ['asset:host:query'], force);
|
||||
},
|
||||
|
||||
// 获取主机密钥列表
|
||||
@@ -123,6 +123,11 @@ export default defineStore('cache', {
|
||||
return await this.load(`${type}_Tags`, () => getTagList(type), undefined, force);
|
||||
},
|
||||
|
||||
// 获取已授权的主机列表
|
||||
async loadAuthorizedHosts(type: HostType = '', force = false) {
|
||||
return await this.load(`authorizedHost_${type || 'ALL'}`, () => getCurrentAuthorizedHost(type), undefined, force);
|
||||
},
|
||||
|
||||
// 获取已授权的主机密钥列表
|
||||
async loadAuthorizedHostKeys(force = false) {
|
||||
return await this.load('authorizedHostKeys', getCurrentAuthorizedHostKey, undefined, force);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 缓存类型
|
||||
export type CacheType = 'users' | 'menus' | 'roles'
|
||||
| 'hostGroups' | 'hostKeys' | 'hostIdentities' | 'host_*'
|
||||
| 'hostGroups' | 'host_*' | 'authorizedHost_*'
|
||||
| 'hostKeys' | 'hostIdentities'
|
||||
| 'dictKeys'
|
||||
| 'execJob'
|
||||
| 'authorizedHostKeys' | 'authorizedHostIdentities'
|
||||
|
||||
@@ -10,16 +10,17 @@ import type {
|
||||
} from './types';
|
||||
import type { ISshSession, ITerminalSession, PanelSessionTabType, TerminalPanelTabItem } from '@/views/host/terminal/types/define';
|
||||
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
||||
import { getCurrentAuthorizedHost } from '@/api/asset/asset-authorized-data';
|
||||
import type { HostQueryResponse } from '@/api/asset/host';
|
||||
import type { TerminalTheme, TerminalThemeSchema } from '@/api/asset/terminal';
|
||||
import { getTerminalThemes } from '@/api/asset/terminal';
|
||||
import { markRaw } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { getPreference, updatePreference } from '@/api/user/preference';
|
||||
import { getLatestConnectHostId } from '@/api/asset/terminal-connect-log';
|
||||
import { nextId } from '@/utils';
|
||||
import { isObject } from '@/utils/is';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { useCacheStore } from '@/store';
|
||||
import { PanelSessionType, TerminalTabs } from '@/views/host/terminal/types/const';
|
||||
import TerminalTabManager from '@/views/host/terminal/handler/terminal-tab-manager';
|
||||
import TerminalSessionManager from '@/views/host/terminal/handler/terminal-session-manager';
|
||||
@@ -73,7 +74,7 @@ export default defineStore('terminal', {
|
||||
hosts: {} as AuthorizedHostQueryResponse,
|
||||
tabManager: new TerminalTabManager(),
|
||||
panelManager: new TerminalPanelManager(),
|
||||
sessionManager: new TerminalSessionManager(),
|
||||
sessionManager: markRaw(new TerminalSessionManager()),
|
||||
transferManager: new SftpTransferManager(),
|
||||
}),
|
||||
|
||||
@@ -130,18 +131,18 @@ export default defineStore('terminal', {
|
||||
},
|
||||
|
||||
// 加载主机列表
|
||||
async loadHosts() {
|
||||
async loadHostList() {
|
||||
if (this.hosts.hostList?.length) {
|
||||
return;
|
||||
}
|
||||
// 查询授权主机
|
||||
const { data } = await getCurrentAuthorizedHost('SSH');
|
||||
const data = await useCacheStore().loadAuthorizedHosts();
|
||||
Object.keys(data).forEach(k => {
|
||||
this.hosts[k as keyof AuthorizedHostQueryResponse] = data[k as keyof AuthorizedHostQueryResponse] as any;
|
||||
});
|
||||
this.hosts.latestHosts = [];
|
||||
// 查询最近连接的主机
|
||||
const { data: latestHosts } = await getLatestConnectHostId('SSH', 30);
|
||||
const { data: latestHosts } = await getLatestConnectHostId('', 30);
|
||||
this.hosts.latestHosts = latestHosts;
|
||||
},
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
|
||||
import { dateFormat } from '@/utils';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@@ -32,8 +31,8 @@ const columns = [
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
align: 'center',
|
||||
width: 106,
|
||||
align: 'left',
|
||||
width: 118,
|
||||
}, {
|
||||
title: '留痕地址',
|
||||
dataIndex: 'address',
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
try {
|
||||
// 加载组内数据
|
||||
const { data } = await getHostGroupRelList(groupId as number);
|
||||
const hosts = await cacheStore.loadHosts('');
|
||||
const hosts = await cacheStore.loadHosts();
|
||||
selectedGroupHosts.value = data.map(s => hosts.find(h => h.id === s) as HostQueryResponse)
|
||||
.filter(Boolean);
|
||||
} catch (e) {
|
||||
@@ -138,6 +138,8 @@
|
||||
idList: checkedGroups.value
|
||||
});
|
||||
Message.success('授权成功');
|
||||
// 清空缓存
|
||||
cacheStore.reset('authorizedHost_ALL', 'authorizedHost_SSH');
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -96,6 +96,8 @@
|
||||
idList: selectedKeys.value
|
||||
});
|
||||
Message.success('授权成功');
|
||||
// 清空缓存
|
||||
cacheStore.reset('authorizedHostIdentities');
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
idList: selectedKeys.value
|
||||
});
|
||||
Message.success('授权成功');
|
||||
// 清空缓存
|
||||
cacheStore.reset('authorizedHostKeys');
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
// 加载主机列表
|
||||
const loadHosts = () => {
|
||||
cacheStore.loadHosts('').then(hosts => {
|
||||
cacheStore.loadHosts().then(hosts => {
|
||||
data.value = hosts.map(s => {
|
||||
return {
|
||||
value: String(s.id),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { dateFormat } from '@/utils';
|
||||
|
||||
const fieldConfig = {
|
||||
rowGap: '10px',
|
||||
labelSpan: 8,
|
||||
labelSpan: 6,
|
||||
minHeight: '22px',
|
||||
fields: [
|
||||
{
|
||||
|
||||
@@ -19,6 +19,8 @@ const columns = [
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
slotName: 'username',
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
}, {
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
@@ -28,6 +30,7 @@ const columns = [
|
||||
title: '主机密钥',
|
||||
dataIndex: 'keyId',
|
||||
slotName: 'keyId',
|
||||
width: 180,
|
||||
}, {
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { dateFormat } from '@/utils';
|
||||
|
||||
const fieldConfig = {
|
||||
rowGap: '10px',
|
||||
labelSpan: 8,
|
||||
labelSpan: 6,
|
||||
minHeight: '22px',
|
||||
fields: [
|
||||
{
|
||||
|
||||
@@ -339,7 +339,8 @@
|
||||
// 重新加载数据
|
||||
fetchCardData();
|
||||
// 清空缓存
|
||||
cacheStore.reset('host_', 'host_SSH');
|
||||
cacheStore.reset('host_ALL', 'host_SSH',
|
||||
'authorizedHost_ALL', 'authorizedHost_SSH');
|
||||
};
|
||||
|
||||
defineExpose({ reload });
|
||||
|
||||
@@ -373,7 +373,8 @@
|
||||
// 重新加载数据
|
||||
fetchTableData();
|
||||
// 清空缓存
|
||||
cacheStore.reset('host_', 'host_SSH');
|
||||
cacheStore.reset('host_ALL', 'host_SSH',
|
||||
'authorizedHost_ALL', 'authorizedHost_SSH');
|
||||
};
|
||||
|
||||
defineExpose({ reload });
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { CardField, CardFieldConfig } from '@/types/card';
|
||||
|
||||
const fieldConfig = {
|
||||
rowGap: '10px',
|
||||
labelSpan: 8,
|
||||
labelSpan: 6,
|
||||
minHeight: '22px',
|
||||
fields: [
|
||||
{
|
||||
|
||||
@@ -135,10 +135,10 @@
|
||||
const { chartOption: terminalConnectChart } = useChartOption(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
left: 8,
|
||||
right: 8,
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
@@ -176,10 +176,10 @@
|
||||
const { chartOption: execCommandChart } = useChartOption(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
left: 8,
|
||||
right: 8,
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="terminal-example" ref="terminal" />
|
||||
<div class="terminal-example" ref="terminalRef" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -11,31 +11,32 @@
|
||||
<script lang="ts" setup>
|
||||
import type { TerminalThemeSchema } from '@/api/asset/terminal';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { markRaw, onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
schema: TerminalThemeSchema | Record<string, any>;
|
||||
}>();
|
||||
|
||||
const terminal = ref();
|
||||
const terminalRef = ref();
|
||||
const term = ref();
|
||||
|
||||
onMounted(() => {
|
||||
term.value = new Terminal({
|
||||
const terminal = new Terminal({
|
||||
theme: { ...props.schema, cursor: props.schema.background },
|
||||
cols: 42,
|
||||
rows: 6,
|
||||
fontSize: 15,
|
||||
cursorInactiveStyle: 'none',
|
||||
});
|
||||
term.value.open(terminal.value);
|
||||
term.value.write(
|
||||
terminal.open(terminalRef.value);
|
||||
terminal.write(
|
||||
'[1;94m[root[0m@[1;96mOrionServer usr]#[0m\r\n' +
|
||||
'dr-xr-xr-x. 2 root root [0m[01;34mbin[0m\r\n' +
|
||||
'dr-xr-xr-x. 2 root root [01;34msbin[0m\r\n' +
|
||||
'drwxr-xr-x. 4 root root [01;34msrc[0m\r\n' +
|
||||
'lrwxrwxrwx. 1 root root [01;36mtmp[0m -> [30;42m../var/tmp[0m '
|
||||
);
|
||||
term.value = markRaw(terminal);
|
||||
});
|
||||
|
||||
defineExpose({ term });
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 已关闭-右侧操作 -->
|
||||
<div v-if="session?.connected === false && closeMessage !== undefined"
|
||||
<div v-if="session?.status.connected === false && closeMessage !== undefined"
|
||||
class="sftp-table-header-right">
|
||||
<!-- 错误信息 -->
|
||||
<a-tag class="close-message"
|
||||
@@ -54,7 +54,7 @@
|
||||
已断开: {{ closeMessage }}
|
||||
</a-tag>
|
||||
<!-- 重连 -->
|
||||
<a-tooltip v-if="session?.canReconnect"
|
||||
<a-tooltip v-if="session?.status.canReconnect"
|
||||
position="top"
|
||||
:mini="true"
|
||||
:overlay-inverse="true"
|
||||
@@ -245,7 +245,7 @@
|
||||
// 设置命令编辑模式
|
||||
const setPathEditable = (editable: boolean) => {
|
||||
// 检查是否断开
|
||||
if (editable && !props.session?.connected) {
|
||||
if (editable && !props.session?.status.connected) {
|
||||
return;
|
||||
}
|
||||
pathEditable.value = editable;
|
||||
@@ -267,7 +267,7 @@
|
||||
// 加载文件列表
|
||||
const loadFileList = (path: string = props.currentPath) => {
|
||||
// 检查是否断开
|
||||
if (!props.session?.connected) {
|
||||
if (!props.session?.status.connected) {
|
||||
return;
|
||||
}
|
||||
emits('loadFile', path);
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
const clickFilename = (record: TableData) => {
|
||||
if (record.isDir) {
|
||||
// 检查是否断开
|
||||
if (!props.session?.connected) {
|
||||
if (!props.session?.status.connected) {
|
||||
return;
|
||||
}
|
||||
// 进入文件夹
|
||||
@@ -218,7 +218,7 @@
|
||||
// 编辑文件
|
||||
const editFile = (record: TableData) => {
|
||||
// 检查是否断开
|
||||
if (!props.session?.connected) {
|
||||
if (!props.session?.status.connected) {
|
||||
return;
|
||||
}
|
||||
emits('editFile', record.name, record.path);
|
||||
@@ -228,7 +228,7 @@
|
||||
// 删除文件
|
||||
const deleteFile = (path: string) => {
|
||||
// 检查是否断开
|
||||
if (!props.session?.connected) {
|
||||
if (!props.session?.status.connected) {
|
||||
return;
|
||||
}
|
||||
emits('deleteFile', [path]);
|
||||
@@ -237,7 +237,7 @@
|
||||
// 下载文件
|
||||
const downloadFile = (path: string) => {
|
||||
// 检查是否断开
|
||||
if (!props.session?.connected) {
|
||||
if (!props.session?.status.connected) {
|
||||
return;
|
||||
}
|
||||
emits('download', [path], false);
|
||||
@@ -246,7 +246,7 @@
|
||||
// 移动文件
|
||||
const moveFile = (path: string) => {
|
||||
// 检查是否断开
|
||||
if (!props.session?.connected) {
|
||||
if (!props.session?.status.connected) {
|
||||
return;
|
||||
}
|
||||
openSftpMoveModal(props.session?.sessionId as string, path);
|
||||
@@ -255,7 +255,7 @@
|
||||
// 文件提权
|
||||
const chmodFile = (path: string, permission: number) => {
|
||||
// 检查是否断开
|
||||
if (!props.session?.connected) {
|
||||
if (!props.session?.status.connected) {
|
||||
return;
|
||||
}
|
||||
openSftpChmodModal(props.session?.sessionId as string, path, permission);
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<!-- 连接状态 -->
|
||||
<a-badge v-if="preference.actionBarSetting.connectStatus !== false"
|
||||
class="status-bridge"
|
||||
:status="getDictValue(sessionStatusKey, session ? session.status : 0, 'status')"
|
||||
:text="getDictValue(sessionStatusKey, session ? session.status : 0)" />
|
||||
:status="getDictValue(sessionStatusKey, session ? session.status.connectStatus : 0, 'status')"
|
||||
:text="getDictValue(sessionStatusKey, session ? session.status.connectStatus : 0)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
// 发送命令
|
||||
const writeCommand = (value: string) => {
|
||||
if (session.value?.canWrite) {
|
||||
if (session.value?.status.canWrite) {
|
||||
session.value?.handler.pasteTrimEnd(value);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,50 +1,66 @@
|
||||
import type { ITerminalSession, TerminalPanelTabItem } from '../types/define';
|
||||
import type { ITerminalSession, TerminalPanelTabItem, TerminalStatus } from '../types/define';
|
||||
import type { Reactive } from 'vue';
|
||||
import { reactive } from 'vue';
|
||||
import { TerminalSessionStatus } from '@/views/host/terminal/types/const';
|
||||
|
||||
// 会话基类
|
||||
export default abstract class BaseSession implements ITerminalSession {
|
||||
export default abstract class BaseSession<Status extends TerminalStatus> implements ITerminalSession<Status> {
|
||||
|
||||
public type: string;
|
||||
public hostId: number;
|
||||
public title: string;
|
||||
public address: string;
|
||||
public readonly type: string;
|
||||
public readonly hostId: number;
|
||||
public readonly title: string;
|
||||
public readonly address: string;
|
||||
public readonly status: Reactive<Status>;
|
||||
public sessionId: string;
|
||||
public connected: boolean;
|
||||
public canReconnect: boolean;
|
||||
public canWrite: boolean;
|
||||
|
||||
protected constructor(type: string, tab: TerminalPanelTabItem) {
|
||||
protected constructor(type: string, tab: TerminalPanelTabItem, status: Partial<Status>) {
|
||||
this.type = type;
|
||||
this.hostId = tab.hostId;
|
||||
this.title = tab.title;
|
||||
this.address = tab.address;
|
||||
this.sessionId = tab.sessionId;
|
||||
this.connected = false;
|
||||
this.canWrite = false;
|
||||
this.canReconnect = false;
|
||||
}
|
||||
|
||||
// 设置是否可写
|
||||
setCanWrite(canWrite: boolean): void {
|
||||
this.canWrite = canWrite;
|
||||
}
|
||||
|
||||
// 设置已连接
|
||||
setConnected(): void {
|
||||
this.connected = true;
|
||||
this.status = reactive({
|
||||
connectStatus: TerminalSessionStatus.CONNECTING,
|
||||
connected: false,
|
||||
canWrite: false,
|
||||
canReconnect: false,
|
||||
...status,
|
||||
} as Status);
|
||||
}
|
||||
|
||||
// 连接会话
|
||||
connect(): void {
|
||||
this.status.connectStatus = TerminalSessionStatus.CONNECTING;
|
||||
}
|
||||
|
||||
// 断开连接
|
||||
disconnect(): void {
|
||||
this.connected = false;
|
||||
// 设置已关闭
|
||||
this.setClosed();
|
||||
}
|
||||
|
||||
// 关闭
|
||||
close(): void {
|
||||
this.connected = false;
|
||||
// 设置已关闭
|
||||
this.setClosed();
|
||||
}
|
||||
|
||||
// 设置是否可写
|
||||
setCanWrite(canWrite: boolean): void {
|
||||
this.status.canWrite = canWrite;
|
||||
}
|
||||
|
||||
// 设置已连接
|
||||
setConnected(): void {
|
||||
this.status.connected = true;
|
||||
this.status.connectStatus = TerminalSessionStatus.CONNECTED;
|
||||
}
|
||||
|
||||
// 设置已关闭
|
||||
setClosed(): void {
|
||||
this.status.connected = false;
|
||||
this.status.canWrite = false;
|
||||
this.status.connectStatus = TerminalSessionStatus.CLOSED;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ISftpSession, ISftpSessionResolver, ITerminalChannel, TerminalPanelTabItem } from '../types/define';
|
||||
import type { ISftpSession, ISftpSessionResolver, ITerminalChannel, TerminalPanelTabItem, TerminalStatus } from '../types/define';
|
||||
import { h } from 'vue';
|
||||
import { InputProtocol } from '@/types/protocol/terminal.protocol';
|
||||
import { PanelSessionType } from '../types/const';
|
||||
@@ -6,7 +6,7 @@ import { Modal } from '@arco-design/web-vue';
|
||||
import BaseSession from './base-session';
|
||||
|
||||
// sftp 会话实现
|
||||
export default class SftpSession extends BaseSession implements ISftpSession {
|
||||
export default class SftpSession extends BaseSession<TerminalStatus> implements ISftpSession {
|
||||
|
||||
public resolver: ISftpSessionResolver;
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class SftpSession extends BaseSession implements ISftpSession {
|
||||
|
||||
constructor(tab: TerminalPanelTabItem,
|
||||
channel: ITerminalChannel) {
|
||||
super(PanelSessionType.SFTP.type, tab);
|
||||
super(PanelSessionType.SFTP.type, tab, {});
|
||||
this.channel = channel;
|
||||
this.showHiddenFile = false;
|
||||
this.resolver = undefined as unknown as ISftpSessionResolver;
|
||||
@@ -27,13 +27,6 @@ export default class SftpSession extends BaseSession implements ISftpSession {
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
// 设置已连接
|
||||
setConnected(): void {
|
||||
super.setConnected();
|
||||
// 连接回调
|
||||
this.resolver.connectCallback();
|
||||
}
|
||||
|
||||
// 连接会话
|
||||
connect(): void {
|
||||
super.connect();
|
||||
@@ -171,4 +164,11 @@ export default class SftpSession extends BaseSession implements ISftpSession {
|
||||
});
|
||||
}
|
||||
|
||||
// 设置已连接
|
||||
setConnected(): void {
|
||||
super.setConnected();
|
||||
// 连接回调
|
||||
this.resolver.connectCallback();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -91,9 +91,9 @@ export default class SshSessionHandler implements ISshSessionHandler {
|
||||
case 'openSftp':
|
||||
case 'uploadFile':
|
||||
case 'checkAppendMissing':
|
||||
return this.session.canWrite;
|
||||
return this.session.status.canWrite;
|
||||
case 'disconnect':
|
||||
return this.session.connected;
|
||||
return this.session.status.connected;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
@@ -197,7 +197,7 @@ export default class SshSessionHandler implements ISshSessionHandler {
|
||||
// 字号增加
|
||||
private fontSizeAdd(addSize: number) {
|
||||
this.inst.options['fontSize'] = this.inst.options['fontSize'] as number + addSize;
|
||||
if (this.session.connected) {
|
||||
if (this.session.status.connected) {
|
||||
this.session.fit();
|
||||
this.inst.focus();
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import type { UnwrapRef } from 'vue';
|
||||
import type { ISearchOptions } from '@xterm/addon-search';
|
||||
import { SearchAddon } from '@xterm/addon-search';
|
||||
import type { TerminalPreference } from '@/store/modules/terminal/types';
|
||||
import type { ISshSession, ISshSessionHandler, ITerminalChannel, TerminalPanelTabItem, XtermDomRef } from '../types/define';
|
||||
import type { ISshSession, ISshSessionHandler, ITerminalChannel, TerminalPanelTabItem, TerminalStatus, XtermDomRef } from '../types/define';
|
||||
import type { XtermAddons } from '@/types/xterm';
|
||||
import { defaultFontFamily } from '@/types/xterm';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { InputProtocol } from '@/types/protocol/terminal.protocol';
|
||||
import { PanelSessionType, TerminalSessionStatus, TerminalShortcutType } from '../types/const';
|
||||
import { PanelSessionType, TerminalShortcutType } from '../types/const';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||||
@@ -21,12 +21,10 @@ import SshSessionHandler from './ssh-session-handler';
|
||||
import BaseSession from './base-session';
|
||||
|
||||
// ssh 会话实现
|
||||
export default class SshSession extends BaseSession implements ISshSession {
|
||||
export default class SshSession extends BaseSession<TerminalStatus> implements ISshSession {
|
||||
|
||||
public inst: Terminal;
|
||||
|
||||
public status: number;
|
||||
|
||||
public handler: ISshSessionHandler;
|
||||
|
||||
private readonly channel: ITerminalChannel;
|
||||
@@ -38,10 +36,9 @@ export default class SshSession extends BaseSession implements ISshSession {
|
||||
constructor(tab: TerminalPanelTabItem,
|
||||
channel: ITerminalChannel,
|
||||
canUseWebgl: boolean) {
|
||||
super(PanelSessionType.SSH.type, tab);
|
||||
super(PanelSessionType.SSH.type, tab, {});
|
||||
this.channel = channel;
|
||||
this.canUseWebgl = canUseWebgl;
|
||||
this.status = TerminalSessionStatus.CONNECTING;
|
||||
this.inst = undefined as unknown as Terminal;
|
||||
this.handler = undefined as unknown as ISshSessionHandler;
|
||||
this.addons = {} as XtermAddons;
|
||||
@@ -93,9 +90,9 @@ export default class SshSession extends BaseSession implements ISshSession {
|
||||
e.preventDefault();
|
||||
}
|
||||
// 检查重新连接
|
||||
if (!this.connected && this.canReconnect && e.key === 'Enter') {
|
||||
if (!this.status.connected && this.status.canReconnect && e.key === 'Enter') {
|
||||
// 防止重复回车
|
||||
this.canReconnect = false;
|
||||
this.status.canReconnect = false;
|
||||
// 异步作用域重新连接
|
||||
setTimeout(async () => {
|
||||
await useTerminalStore().reOpenSession(this.sessionId);
|
||||
@@ -120,7 +117,7 @@ export default class SshSession extends BaseSession implements ISshSession {
|
||||
private registerEvent(dom: HTMLElement, preference: UnwrapRef<TerminalPreference>) {
|
||||
// 注册输入事件
|
||||
this.inst.onData(s => {
|
||||
if (!this.canWrite || !this.connected) {
|
||||
if (!this.status.canWrite || !this.status.connected) {
|
||||
return;
|
||||
}
|
||||
// 输入
|
||||
@@ -145,7 +142,7 @@ export default class SshSession extends BaseSession implements ISshSession {
|
||||
}
|
||||
// 注册 resize 事件
|
||||
this.inst.onResize(({ cols, rows }) => {
|
||||
if (!this.connected) {
|
||||
if (!this.status.connected) {
|
||||
return;
|
||||
}
|
||||
this.channel.send(InputProtocol.SSH_RESIZE, {
|
||||
@@ -158,7 +155,7 @@ export default class SshSession extends BaseSession implements ISshSession {
|
||||
addEventListen(dom, 'contextmenu', async () => {
|
||||
// 右键粘贴逻辑
|
||||
if (preference.interactSetting.rightClickPaste) {
|
||||
if (!this.canWrite || !this.connected) {
|
||||
if (!this.status.canWrite || !this.status.connected) {
|
||||
return;
|
||||
}
|
||||
// 未开启右键选中 || 开启并无选中的内容则粘贴
|
||||
@@ -204,29 +201,9 @@ export default class SshSession extends BaseSession implements ISshSession {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置已连接
|
||||
setConnected(): void {
|
||||
super.setConnected();
|
||||
// 设置状态
|
||||
this.status = TerminalSessionStatus.CONNECTED;
|
||||
this.inst.focus();
|
||||
}
|
||||
|
||||
// 设置是否可写
|
||||
setCanWrite(canWrite: boolean): void {
|
||||
super.setCanWrite(canWrite);
|
||||
if (canWrite) {
|
||||
this.inst.options.cursorBlink = useTerminalStore().preference.displaySetting.cursorBlink;
|
||||
} else {
|
||||
this.inst.options.cursorBlink = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 连接会话
|
||||
connect(): void {
|
||||
super.connect();
|
||||
// 设置状态
|
||||
this.status = TerminalSessionStatus.CONNECTING;
|
||||
// 发送会话初始化请求
|
||||
this.channel.send(InputProtocol.CHECK, {
|
||||
sessionId: this.sessionId,
|
||||
@@ -295,4 +272,20 @@ export default class SshSession extends BaseSession implements ISshSession {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置已连接
|
||||
setConnected(): void {
|
||||
super.setConnected();
|
||||
this.inst.focus();
|
||||
}
|
||||
|
||||
// 设置是否可写
|
||||
setCanWrite(canWrite: boolean): void {
|
||||
super.setCanWrite(canWrite);
|
||||
if (canWrite) {
|
||||
this.inst.options.cursorBlink = useTerminalStore().preference.displaySetting.cursorBlink;
|
||||
} else {
|
||||
this.inst.options.cursorBlink = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export default class TerminalChannel implements ITerminalChannel {
|
||||
private closeCallback(): void {
|
||||
// 关闭时将手动触发 close 消息, 有可能是其他原因关闭的, 没有接收到 close 消息, 导致已断开是终端还是显示已连接
|
||||
Object.values(this.sessionManager.sessions).forEach(s => {
|
||||
if (!s?.connected) {
|
||||
if (!s?.status.connected) {
|
||||
return;
|
||||
}
|
||||
// close 消息
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ISftpSession, ISshSession, ITerminalChannel, ITerminalOutputProcessor, ITerminalSession, ITerminalSessionManager } from '../types/define';
|
||||
import type { OutputPayload } from '@/types/protocol/terminal.protocol';
|
||||
import { InputProtocol } from '@/types/protocol/terminal.protocol';
|
||||
import { PanelSessionType, TerminalSessionStatus } from '../types/const';
|
||||
import { PanelSessionType } from '../types/const';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
@@ -21,7 +21,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
processCheck({ sessionId, result, msg }: OutputPayload): void {
|
||||
const success = !!Number.parseInt(result);
|
||||
const session = this.sessionManager.getSession(sessionId);
|
||||
session.canReconnect = !success;
|
||||
session.status.canReconnect = !success;
|
||||
// 处理
|
||||
this.processWithType(session, ssh => {
|
||||
// ssh 会话
|
||||
@@ -35,9 +35,10 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
rows: ssh.inst.rows
|
||||
});
|
||||
} else {
|
||||
// 设置已关闭
|
||||
session.setClosed();
|
||||
// 未成功展示错误信息
|
||||
ssh.write(`[91m${msg || ''}[0m\r\n\r\n[91m输入回车重新连接...[0m\r\n\r\n`);
|
||||
ssh.status = TerminalSessionStatus.CLOSED;
|
||||
}
|
||||
}, sftp => {
|
||||
// sftp 会话
|
||||
@@ -47,6 +48,8 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
sessionId,
|
||||
});
|
||||
} else {
|
||||
// 设置已关闭
|
||||
session.setClosed();
|
||||
// 未成功提示错误信息
|
||||
sftp.resolver?.onClose(false, msg);
|
||||
Message.error(msg || '建立 SFTP 失败');
|
||||
@@ -58,29 +61,25 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
processConnect({ sessionId, result, msg }: OutputPayload): void {
|
||||
const success = !!Number.parseInt(result);
|
||||
const session = this.sessionManager.getSession(sessionId);
|
||||
session.canReconnect = !success;
|
||||
session.status.canReconnect = !success;
|
||||
if (success) {
|
||||
// 设置可写
|
||||
session.setCanWrite(true);
|
||||
// 设置已连接
|
||||
session.setConnected();
|
||||
} else {
|
||||
// 设置已关闭
|
||||
session.setClosed();
|
||||
}
|
||||
// 处理
|
||||
this.processWithType(session, ssh => {
|
||||
// ssh 会话
|
||||
if (success) {
|
||||
// 设置可写
|
||||
ssh.setCanWrite(true);
|
||||
// 设置已连接
|
||||
ssh.setConnected();
|
||||
} else {
|
||||
// 未成功展示错误信息
|
||||
if (!success) {
|
||||
// ssh 会话 未成功展示错误信息
|
||||
ssh.write(`[91m${msg || ''}[0m\r\n\r\n[91m输入回车重新连接...[0m\r\n\r\n`);
|
||||
ssh.status = TerminalSessionStatus.CLOSED;
|
||||
}
|
||||
}, sftp => {
|
||||
// sftp 会话
|
||||
if (success) {
|
||||
// 设置可写
|
||||
sftp.setCanWrite(true);
|
||||
// 设置已连接
|
||||
sftp.setConnected();
|
||||
} else {
|
||||
// 未成功提示错误信息
|
||||
if (!success) {
|
||||
// sftp 会话 未成功提示错误信息
|
||||
sftp.resolver?.onClose(false, msg);
|
||||
Message.error(msg || '打开 SFTP 失败');
|
||||
}
|
||||
@@ -95,8 +94,9 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
return;
|
||||
}
|
||||
const isForceClose = !!Number.parseInt(forceClose);
|
||||
session.connected = false;
|
||||
session.canReconnect = !isForceClose;
|
||||
session.status.canReconnect = !isForceClose;
|
||||
// 设置已关闭
|
||||
session.setClosed();
|
||||
// 处理
|
||||
this.processWithType(session, ssh => {
|
||||
// ssh 拼接关闭消息
|
||||
@@ -104,13 +104,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
if (!isForceClose) {
|
||||
ssh.write('[91m输入回车重新连接...[0m\r\n\r\n');
|
||||
}
|
||||
// 设置状态
|
||||
ssh.status = TerminalSessionStatus.CLOSED;
|
||||
// 设置不可写
|
||||
ssh.setCanWrite(false);
|
||||
}, sftp => {
|
||||
// 设置不可写
|
||||
sftp.setCanWrite(false);
|
||||
// sftp 设置状态
|
||||
sftp.resolver?.onClose(isForceClose, msg);
|
||||
});
|
||||
|
||||
@@ -104,7 +104,10 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
|
||||
// 移除 session
|
||||
this.sessions[sessionId] = undefined as unknown as ITerminalSession;
|
||||
// session 全部关闭后 关闭 channel
|
||||
if (Object.values(this.sessions).filter(Boolean).every(s => !s?.connected)) {
|
||||
const allClosed = Object.values(this.sessions)
|
||||
.filter(Boolean)
|
||||
.every(s => !s?.status.connected);
|
||||
if (allClosed) {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
const {
|
||||
fetchPreference, getCurrentSession, openSession,
|
||||
layoutState, preference, loadHosts, hosts, tabManager, sessionManager
|
||||
layoutState, preference, loadHostList, hosts, tabManager, sessionManager
|
||||
} = useTerminalStore();
|
||||
const { loading, setLoading } = useLoading(true);
|
||||
const { enter: enterFull, exit: exitFull } = useFullscreen();
|
||||
@@ -165,7 +165,7 @@
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 加载主机
|
||||
await loadHosts();
|
||||
await loadHostList();
|
||||
// 默认连接主机
|
||||
const connect = route.query.connect as string;
|
||||
if (connect) {
|
||||
|
||||
@@ -363,7 +363,7 @@ export const TransferReceiver = {
|
||||
};
|
||||
|
||||
// 会话关闭信息
|
||||
export const sessionCloseMsg = 'session closed...';
|
||||
export const sessionCloseMsg = '会话已结束...';
|
||||
|
||||
// 打开 settingModal key
|
||||
export const openSettingModalKey = Symbol();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Terminal } from '@xterm/xterm';
|
||||
import type { ISearchOptions } from '@xterm/addon-search';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import type { CSSProperties, Reactive } from 'vue';
|
||||
import type { HostQueryResponse } from '@/api/asset/host';
|
||||
import type { InputPayload, OutputPayload, Protocol } from '@/types/protocol/terminal.protocol';
|
||||
|
||||
@@ -196,38 +196,47 @@ export interface XtermDomRef {
|
||||
uploadModal: any;
|
||||
}
|
||||
|
||||
// 终端会话定义
|
||||
export interface ITerminalSession {
|
||||
type: string;
|
||||
title: string;
|
||||
address: string;
|
||||
hostId: number;
|
||||
sessionId: string;
|
||||
// 终端状态
|
||||
export interface TerminalStatus {
|
||||
// 连接状态
|
||||
connectStatus: number;
|
||||
// 是否已连接
|
||||
connected: boolean;
|
||||
// 是否可以重新连接
|
||||
canReconnect: boolean;
|
||||
// 是否可写
|
||||
canWrite: boolean;
|
||||
// 是否可以重新连接
|
||||
canReconnect: boolean;
|
||||
}
|
||||
|
||||
// 终端会话定义
|
||||
export interface ITerminalSession<Status extends TerminalStatus = TerminalStatus> {
|
||||
readonly type: string;
|
||||
readonly title: string;
|
||||
readonly address: string;
|
||||
readonly hostId: number;
|
||||
// 终端状态
|
||||
readonly status: Reactive<Status>;
|
||||
sessionId: string;
|
||||
|
||||
// 设置是否可写
|
||||
setCanWrite: (canWrite: boolean) => void;
|
||||
// 设置已连接
|
||||
setConnected: () => void;
|
||||
// 连接会话
|
||||
connect: () => void;
|
||||
// 断开连接
|
||||
disconnect: () => void;
|
||||
// 关闭
|
||||
close: () => void;
|
||||
|
||||
// 设置是否可写
|
||||
setCanWrite: (canWrite: boolean) => void;
|
||||
// 设置已连接
|
||||
setConnected: () => void;
|
||||
// 设置已关闭
|
||||
setClosed: () => void;
|
||||
}
|
||||
|
||||
// ssh 会话定义
|
||||
export interface ISshSession extends ITerminalSession {
|
||||
// terminal 实例
|
||||
inst: Terminal;
|
||||
// 状态
|
||||
status: number;
|
||||
// 处理器
|
||||
handler: ISshSessionHandler;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user