Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
lijiahangmax
2025-03-06 23:29:28 +08:00
16 changed files with 195 additions and 151 deletions

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,7 @@ 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';
@@ -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(),
}),

View File

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

View File

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

View File

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

View File

@@ -64,7 +64,7 @@
// 发送命令
const writeCommand = (value: string) => {
if (session.value?.canWrite) {
if (session.value?.status.canWrite) {
session.value?.handler.pasteTrimEnd(value);
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(`${msg || ''}\r\n\r\n输入回车重新连接...\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(`${msg || ''}\r\n\r\n输入回车重新连接...\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('输入回车重新连接...\r\n\r\n');
}
// 设置状态
ssh.status = TerminalSessionStatus.CLOSED;
// 设置不可写
ssh.setCanWrite(false);
}, sftp => {
// 设置不可写
sftp.setCanWrite(false);
// sftp 设置状态
sftp.resolver?.onClose(isForceClose, msg);
});

View File

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

View File

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