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