Merge pull request '重构开发容器创建过程' (#3) from fix/issue-32 into main
Some checks failed
docker / build (push) Failing after 2m44s
Some checks failed
docker / build (push) Failing after 2m44s
Reviewed-on: #3
This commit is contained in:
@@ -16,22 +16,15 @@ interface State {
|
||||
export class Terminal extends Component<Props, State> {
|
||||
private container: HTMLElement;
|
||||
private xterm: Xterm;
|
||||
private intervalID: NodeJS.Timeout;
|
||||
private pollingIntervalID: NodeJS.Timeout | null = null;
|
||||
private currentDevcontainer = {
|
||||
title: 'Devcontainer Info',
|
||||
detail: 'No Devcontainer Created yet',
|
||||
port: '',
|
||||
ip:'',
|
||||
steps: [
|
||||
// {
|
||||
// summary: '',
|
||||
// duration: '',
|
||||
// status: '',
|
||||
// logs:{
|
||||
// },
|
||||
// }
|
||||
],
|
||||
state: '-1', // 容器状态 (0-5, -1)
|
||||
steps: [] as any[],
|
||||
};
|
||||
private isDockerType = false;
|
||||
private apiBaseUrl = '';
|
||||
private apiParams = new URLSearchParams();
|
||||
|
||||
constructor(props: Props) {
|
||||
super();
|
||||
this.xterm = new Xterm(props, this.showModal);
|
||||
@@ -41,44 +34,19 @@ export class Terminal extends Component<Props, State> {
|
||||
await this.xterm.refreshToken();
|
||||
const options = new URLSearchParams(decodeURIComponent(window.location.search));
|
||||
|
||||
const params = new URLSearchParams({
|
||||
this.isDockerType = options.get('type') === 'docker';
|
||||
this.apiBaseUrl = `http://${options.get('domain')}:${options.get('port')}/${options.get('user')}/${options.get('repo')}`;
|
||||
this.apiParams = new URLSearchParams({
|
||||
repo: options.get('repoid') as string,
|
||||
user: options.get('userid') as string,
|
||||
});
|
||||
fetch('http://' +
|
||||
options.get('domain') +
|
||||
':'+
|
||||
options.get('port') +
|
||||
'/' +
|
||||
options.get('user') +
|
||||
'/' +
|
||||
options.get('repo') +
|
||||
'/devcontainer/status?' +
|
||||
params
|
||||
)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status !== '-1') {
|
||||
if (options.get('type') === 'docker') {
|
||||
this.xterm.open(this.container);
|
||||
this.xterm.changeContainerStatus(data.status);
|
||||
this.xterm.connect();
|
||||
} else {
|
||||
this.intervalID = setInterval(this.loadOutput, 8000);
|
||||
this.xterm.open(this.container);
|
||||
this.xterm.changeUrl(this.currentDevcontainer.ip, this.currentDevcontainer.port)
|
||||
this.xterm.changeStatus(true);
|
||||
this.xterm.connect();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
|
||||
// 首次检查状态,根据状态启动相应的轮询策略
|
||||
await this.checkStatusAndStartPolling();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.stopPolling();
|
||||
this.xterm.dispose();
|
||||
}
|
||||
|
||||
@@ -107,47 +75,237 @@ export class Terminal extends Component<Props, State> {
|
||||
if (files) this.xterm.sendFile(files);
|
||||
}
|
||||
|
||||
/**
|
||||
* 首次状态检查并启动相应的轮询策略
|
||||
*
|
||||
* 流程:
|
||||
* 1. 状态 0-4: 启动日志轮询,显示容器创建日志
|
||||
* - 日志轮询会在状态变为 5 时自动停止并获取连接命令
|
||||
* - 或者检测到 DEVCONTAINER_STARTUP_COMPLETE 后主动检查状态并获取连接命令
|
||||
* 2. 状态 5: 直接获取并执行连接命令(只执行一次,不需要轮询)
|
||||
*/
|
||||
@bind
|
||||
private loadOutput() {
|
||||
const options = new URLSearchParams(decodeURIComponent(window.location.search));
|
||||
const params = new URLSearchParams({
|
||||
repo: options.get('repoid') as string,
|
||||
user: options.get('userid') as string,
|
||||
});
|
||||
|
||||
fetch(
|
||||
'http://' + options.get('domain') + ':'+ options.get('port') +'/' +
|
||||
options.get('user') +
|
||||
'/' +
|
||||
options.get('repo') +
|
||||
'/devcontainer/output?' +
|
||||
params
|
||||
)
|
||||
.then(response => response.json())
|
||||
.then(job => {
|
||||
if (!job) {
|
||||
clearInterval(this.intervalID);
|
||||
this.intervalID = null as any;
|
||||
return;
|
||||
private async checkStatusAndStartPolling() {
|
||||
try {
|
||||
const response = await fetch(`${this.apiBaseUrl}/devcontainer/status?${this.apiParams}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === '-1') {
|
||||
// 容器不存在,不启动轮询
|
||||
return;
|
||||
}
|
||||
|
||||
// 打开终端并连接 WebSocket
|
||||
this.xterm.open(this.container);
|
||||
if (this.isDockerType) {
|
||||
this.xterm.changeContainerStatus(data.status);
|
||||
}
|
||||
this.xterm.connect();
|
||||
|
||||
const status = parseInt(data.status);
|
||||
|
||||
if (this.isDockerType) {
|
||||
// Docker 类型:根据状态选择轮询策略
|
||||
if (status >= 0 && status < 5) {
|
||||
// 状态 0-4: 创建中,启动日志轮询(3秒一次)
|
||||
// 日志轮询会在状态变为 5 时自动停止并获取连接命令
|
||||
this.startLogPolling(3000);
|
||||
} else if (status === 5) {
|
||||
// 状态 5: 创建完成,直接获取并执行连接命令
|
||||
this.loadCommandOnce();
|
||||
}
|
||||
if(this.currentDevcontainer.steps.length < job.currentDevcontainer.steps.length){
|
||||
for(let i = this.currentDevcontainer.steps.length; i < job.currentDevcontainer.steps.length; i++) {
|
||||
this.xterm.writeData(job.currentDevcontainer.steps[i].summary);
|
||||
this.xterm.writeData('\r\n');
|
||||
for(let j = 0; j < job.currentDevcontainer.steps[i].logs.length; j++) {
|
||||
this.xterm.writeData(job.currentDevcontainer.steps[i].logs[j].message.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'));
|
||||
this.xterm.writeData('\r\n');
|
||||
} else {
|
||||
// 非 Docker 类型:直接启动日志轮询
|
||||
this.startLogPolling(8000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动日志轮询(用于显示容器创建日志)
|
||||
*/
|
||||
@bind
|
||||
private startLogPolling(interval: number = 1000) {
|
||||
this.stopPolling();
|
||||
// 立即加载一次日志
|
||||
this.loadLogs();
|
||||
// 然后定期轮询
|
||||
this.pollingIntervalID = setInterval(this.loadLogs, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取并执行连接命令(只执行一次,不需要轮询)
|
||||
*/
|
||||
@bind
|
||||
private loadCommandOnce() {
|
||||
this.stopPolling();
|
||||
this.xterm.loadCommandOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止所有轮询
|
||||
*/
|
||||
@bind
|
||||
private stopPolling() {
|
||||
if (this.pollingIntervalID) {
|
||||
clearInterval(this.pollingIntervalID);
|
||||
this.pollingIntervalID = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化日志消息的换行符
|
||||
*/
|
||||
private normalizeLogMessage(message: string): string {
|
||||
return message.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出日志行
|
||||
*/
|
||||
private writeLogLine(message: string) {
|
||||
this.xterm.writeData(this.normalizeLogMessage(message));
|
||||
this.xterm.writeData('\r\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理步骤日志更新
|
||||
* @returns
|
||||
*/
|
||||
private processStepLogs(newSteps: any[], oldSteps: any[]): boolean {
|
||||
let detectedStartupComplete = false;
|
||||
|
||||
for (let i = 0; i < newSteps.length; i++) {
|
||||
const newStep = newSteps[i];
|
||||
const oldStep = oldSteps[i] as any;
|
||||
|
||||
// 如果是新步骤,输出步骤标题
|
||||
if (!oldStep) {
|
||||
// 如果不是第一个步骤,在前面添加换行
|
||||
if (i > 0) {
|
||||
this.xterm.writeData('\r\n');
|
||||
}
|
||||
this.xterm.writeData(`${newStep.summary}\r\n`);
|
||||
// 输出该步骤的所有日志
|
||||
if (newStep.logs && newStep.logs.length > 0) {
|
||||
for (let j = 0; j < newStep.logs.length; j++) {
|
||||
this.writeLogLine(newStep.logs[j].message);
|
||||
// 检测 DEVCONTAINER_STARTUP_COMPLETE
|
||||
if (newStep.logs[j].message.includes('DEVCONTAINER_STARTUP_COMPLETE')) {
|
||||
detectedStartupComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 输出新增的日志行
|
||||
if (newStep.logs && oldStep.logs && newStep.logs.length > oldStep.logs.length) {
|
||||
for (let j = oldStep.logs.length; j < newStep.logs.length; j++) {
|
||||
this.writeLogLine(newStep.logs[j].message);
|
||||
// 检测 DEVCONTAINER_STARTUP_COMPLETE
|
||||
if (newStep.logs[j].message.includes('DEVCONTAINER_STARTUP_COMPLETE')) {
|
||||
detectedStartupComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return detectedStartupComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并处理状态变化
|
||||
*
|
||||
* 日志轮询结束条件:
|
||||
* 1. 状态从 0-4 变为 5:自动停止日志轮询,获取并执行连接命令
|
||||
*
|
||||
* @param newState 新状态(从日志接口返回的 job.currentDevcontainer.state 获取)
|
||||
* @returns 是否已获取连接命令
|
||||
*/
|
||||
private checkAndHandleStatusChange(newState: string): boolean {
|
||||
const prevStatus = parseInt(this.currentDevcontainer.state);
|
||||
const currentStatus = parseInt(newState);
|
||||
|
||||
// 状态变化处理:从创建中(0-4)变为完成(5)时,停止日志轮询并获取连接命令
|
||||
// 状态是从日志接口 (/devcontainer/logs) 返回的数据中获取的
|
||||
if (this.isDockerType && prevStatus >= 0 && prevStatus < 5 && currentStatus === 5) {
|
||||
console.log('[Terminal] Container creation completed (status 0-4 -> 5), stopping log polling and loading command');
|
||||
this.stopPolling();
|
||||
this.loadCommandOnce();
|
||||
return true; // 已切换
|
||||
}
|
||||
return false; // 未切换
|
||||
}
|
||||
|
||||
/**
|
||||
* 主动检查状态(用于检测到 DEVCONTAINER_STARTUP_COMPLETE 后)
|
||||
*
|
||||
* 当检测到 DEVCONTAINER_STARTUP_COMPLETE 但日志接口返回的状态还不是 5 时,
|
||||
* 主动查询状态接口,如果状态为 5,则停止日志轮询并切换到命令轮询
|
||||
*/
|
||||
@bind
|
||||
private async checkStatusOnce() {
|
||||
try {
|
||||
const response = await fetch(`${this.apiBaseUrl}/devcontainer/status?${this.apiParams}`);
|
||||
const data = await response.json();
|
||||
const status = parseInt(data.status);
|
||||
|
||||
if (status === 5) {
|
||||
console.log('[Terminal] Status check: container is ready (status 5), stopping log polling and loading command');
|
||||
this.stopPolling();
|
||||
this.loadCommandOnce();
|
||||
} else {
|
||||
console.log('[Terminal] Status check: container not ready yet (status', status, '), log polling will continue');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载容器创建日志
|
||||
*/
|
||||
@bind
|
||||
private loadLogs() {
|
||||
// 使用日志接口(从内存中获取实时日志)
|
||||
// 日志接口返回的数据包含状态信息:job.currentDevcontainer.state
|
||||
fetch(`${this.apiBaseUrl}/devcontainer/logs?${this.apiParams}`)
|
||||
.then(response => response.json())
|
||||
.then(job => {
|
||||
if (!job || !job.currentDevcontainer) {
|
||||
// 如果日志接口没有数据,停止轮询
|
||||
this.stopPolling();
|
||||
return;
|
||||
}
|
||||
|
||||
const newSteps = job.currentDevcontainer.steps;
|
||||
const oldSteps = this.currentDevcontainer.steps;
|
||||
|
||||
// 处理步骤日志更新,检测是否出现 DEVCONTAINER_STARTUP_COMPLETE
|
||||
const detectedStartupComplete = this.processStepLogs(newSteps, oldSteps);
|
||||
|
||||
// 更新当前状态(从日志接口返回的状态)
|
||||
this.currentDevcontainer = job.currentDevcontainer;
|
||||
if (this.currentDevcontainer.detail === '4' && this.intervalID) {
|
||||
clearInterval(this.intervalID);
|
||||
this.intervalID = null as any;
|
||||
|
||||
// 检查并处理状态变化:从日志接口返回的状态中检测,如果状态变为 5,停止日志轮询并切换到命令轮询
|
||||
const statusChanged = this.checkAndHandleStatusChange(this.currentDevcontainer.state);
|
||||
|
||||
// 如果检测到 DEVCONTAINER_STARTUP_COMPLETE 但状态还没变为 5,主动检查一次状态
|
||||
// 因为状态更新可能有延迟,需要主动轮询状态接口
|
||||
if (detectedStartupComplete && !statusChanged && this.isDockerType) {
|
||||
console.log('[Terminal] Detected DEVCONTAINER_STARTUP_COMPLETE but status not 5 yet, checking status...');
|
||||
// 延迟一点再检查,给后端一点时间更新状态
|
||||
setTimeout(() => {
|
||||
this.checkStatusOnce();
|
||||
}, 500);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
console.error('Error loading logs:', error);
|
||||
// 如果日志接口失败,停止轮询
|
||||
this.stopPolling();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,6 @@ export class Xterm {
|
||||
private containerStatus = "";
|
||||
private attachCommandSent = false;
|
||||
private attachCommandSentAt?: number;
|
||||
private beforeCommand?: string;
|
||||
constructor(
|
||||
private options: XtermOptions,
|
||||
private sendCb: () => void
|
||||
@@ -284,19 +283,11 @@ export class Xterm {
|
||||
register(addEventListener(socket, 'message', this.onSocketData as EventListener));
|
||||
register(addEventListener(socket, 'close', this.onSocketClose as EventListener));
|
||||
register(addEventListener(socket, 'error', () => (this.doReconnect = false)));
|
||||
const options = new URLSearchParams(decodeURIComponent(window.location.search));
|
||||
if (options.get('type') === 'docker') {
|
||||
if(this.containerStatus === '4' || this.containerStatus === '-1'){
|
||||
this.intervalID = setInterval(this.loadCommand, 1000);
|
||||
}else{
|
||||
this.intervalID = setInterval(this.loadCommand, 8000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@bind
|
||||
private onSocketOpen() {
|
||||
console.log('[ttyd] websocket connection opened');
|
||||
console.log('[webTerminal] WebSocket opened, containerStatus:', this.containerStatus, 'connectStatus:', this.connectStatus, 'attachCommandSent:', this.attachCommandSent);
|
||||
|
||||
const { textEncoder, terminal, overlayAddon } = this;
|
||||
const msg = JSON.stringify({ AuthToken: this.token, columns: terminal.cols, rows: terminal.rows });
|
||||
@@ -306,6 +297,12 @@ export class Xterm {
|
||||
terminal.reset();
|
||||
terminal.options.disableStdin = false;
|
||||
overlayAddon.showOverlay('Reconnected', 300);
|
||||
// 重新连接后,如果状态是5且未连接,重置连接状态以便重新发送连接命令
|
||||
if (this.containerStatus === '5' && !this.connectStatus) {
|
||||
console.log('[webTerminal] Reconnected, resetting attach command state');
|
||||
this.attachCommandSent = false;
|
||||
this.attachCommandSentAt = undefined;
|
||||
}
|
||||
} else {
|
||||
this.opened = true;
|
||||
}
|
||||
@@ -343,57 +340,89 @@ export class Xterm {
|
||||
}
|
||||
}
|
||||
|
||||
@bind
|
||||
private loadCommand() {
|
||||
/**
|
||||
* 获取 URL 查询参数
|
||||
*/
|
||||
private getUrlParams(): { options: URLSearchParams; params: URLSearchParams; baseUrl: string } {
|
||||
const options = new URLSearchParams(decodeURIComponent(window.location.search));
|
||||
const params = new URLSearchParams({
|
||||
repo: options.get('repoid') as string,
|
||||
user: options.get('userid') as string,
|
||||
});
|
||||
const baseUrl = `http://${options.get('domain')}:${options.get('port')}/${options.get('user')}/${options.get('repo')}`;
|
||||
return { options, params, baseUrl };
|
||||
}
|
||||
|
||||
fetch(
|
||||
'http://' + options.get('domain') + ':'+ options.get('port') +'/' +
|
||||
options.get('user') +
|
||||
'/' +
|
||||
options.get('repo') +
|
||||
'/devcontainer/command?' +
|
||||
params
|
||||
)
|
||||
.then(response => response.json())
|
||||
/**
|
||||
* 获取并执行连接容器的命令(带重试机制)
|
||||
*
|
||||
* 重试机制:
|
||||
* - 最多重试 5 次
|
||||
* - 每次重试间隔递增(1s, 2s, 3s, 4s, 5s)
|
||||
* - 如果成功获取命令,立即执行并停止重试
|
||||
*/
|
||||
@bind
|
||||
public loadCommandOnce() {
|
||||
this.loadCommandWithRetry(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带重试的命令获取
|
||||
* @param retryCount 当前重试次数
|
||||
*/
|
||||
@bind
|
||||
private loadCommandWithRetry(retryCount: number = 0) {
|
||||
const maxRetries = 5;
|
||||
const { params, baseUrl } = this.getUrlParams();
|
||||
|
||||
fetch(`${baseUrl}/devcontainer/command?${params}`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// 验证数据有效性
|
||||
if (!data || !data.command) {
|
||||
throw new Error('Invalid command data received');
|
||||
}
|
||||
|
||||
if (this.workdir === ''){
|
||||
this.workdir = data.workdir;
|
||||
}
|
||||
if (data.status !== '4' && data.status !== '0') {
|
||||
if(this.containerStatus !== data.status){
|
||||
this.sendData(data.command);
|
||||
}
|
||||
this.containerStatus = data.status;
|
||||
} else {
|
||||
if (this.containerStatus !== '4'){
|
||||
this.writeData("\x1b[31mCreation completed.\x1b[0m\r\n");
|
||||
}
|
||||
this.containerStatus = data.status;
|
||||
if (data.status === '4') {
|
||||
const parts = data.command.split('\n');
|
||||
const shouldResend = this.attachCommandSent && this.attachCommandSentAt !== undefined && Date.now() - this.attachCommandSentAt > 5000;
|
||||
if ((!this.attachCommandSent || shouldResend) && !this.connectStatus && parts[0]) {
|
||||
this.sendData(parts[0]+"\n");
|
||||
this.attachCommandSent = true;
|
||||
this.attachCommandSentAt = Date.now();
|
||||
}
|
||||
this.postAttachCommand = parts;
|
||||
}
|
||||
|
||||
// 执行连接容器的命令(只执行一次)
|
||||
const parts = data.command.split('\n');
|
||||
if (parts[0] && !this.connectStatus) {
|
||||
console.log('[Xterm] Successfully loaded connection command, executing...');
|
||||
this.sendData(parts[0]+"\n");
|
||||
this.attachCommandSent = true;
|
||||
this.attachCommandSentAt = Date.now();
|
||||
}
|
||||
this.postAttachCommand = parts;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
console.error(`[Xterm] Error loading command (attempt ${retryCount + 1}/${maxRetries}):`, error);
|
||||
|
||||
// 如果还有重试次数,继续重试
|
||||
if (retryCount < maxRetries - 1) {
|
||||
const delay = (retryCount + 1) * 1000; // 递增延迟:1s, 2s, 3s, 4s, 5s
|
||||
console.log(`[Xterm] Retrying command load in ${delay}ms...`);
|
||||
setTimeout(() => {
|
||||
this.loadCommandWithRetry(retryCount + 1);
|
||||
}, delay);
|
||||
} else {
|
||||
console.error('[Xterm] Failed to load command after all retries');
|
||||
// 可以在这里显示错误提示给用户
|
||||
}
|
||||
});
|
||||
}
|
||||
@bind
|
||||
public changeContainerStatus(v: string){
|
||||
this.containerStatus = v;
|
||||
}
|
||||
|
||||
@bind
|
||||
private parseOptsFromUrlQuery(query: string): Preferences {
|
||||
const { terminal } = this;
|
||||
@@ -439,11 +468,7 @@ export class Xterm {
|
||||
const decodedData = textDecoder.decode(data);
|
||||
console.log('[ttyd] output:', decodedData);
|
||||
const compactOutput = decodedData.replace(/\s/g, '');
|
||||
const options = new URLSearchParams(decodeURIComponent(window.location.search));
|
||||
const params = new URLSearchParams({
|
||||
repo: options.get('repoid') as string,
|
||||
user: options.get('userid') as string,
|
||||
});
|
||||
const { options } = this.getUrlParams();
|
||||
if (options.get('type') === 'docker') {
|
||||
// 保存host的标题
|
||||
if (this.hostTitle === ''){
|
||||
@@ -457,8 +482,8 @@ export class Xterm {
|
||||
this.attachCommandSentAt = undefined;
|
||||
this.postAttachCommandStatus = false;
|
||||
}
|
||||
// this.connectStatus = true 连接完成
|
||||
//由于第二条docker命令中包含Successfully connected to the devcontainer,需要过滤否则会导致轮询终止,卡在状态2
|
||||
// 检测连接完成:监听 "Successfully connected to the devcontainer" 消息
|
||||
// 这条消息是由连接命令中的 echo "$WEB_TERMINAL_HELLO" 输出的
|
||||
if (!this.connectStatus) {
|
||||
const sanitizedOutput = this.stripAnsi(decodedData).replace(/\r/g, '\n');
|
||||
const combinedOutput = this.connectionMessageBuffer + sanitizedOutput;
|
||||
@@ -469,12 +494,12 @@ export class Xterm {
|
||||
this.connectStatus = true;
|
||||
this.connectionMessageBuffer = '';
|
||||
this.attachCommandSentAt = undefined;
|
||||
if (this.intervalID) {
|
||||
clearInterval(this.intervalID);
|
||||
}
|
||||
console.log('[Xterm] Connection established, enabling terminal input');
|
||||
// 确保终端输入已启用
|
||||
this.terminal.options.disableStdin = false;
|
||||
}
|
||||
}
|
||||
// 连接完成之前,不输出标题和docker命令
|
||||
// 连接完成之前,过滤掉 docker exec 命令的标题输出(ANSI 码和 docker-H 开头的输出)
|
||||
if (
|
||||
!(this.connectStatus === false &&
|
||||
(textDecoder.decode(data).includes('\x1b') ||
|
||||
@@ -484,6 +509,7 @@ export class Xterm {
|
||||
}
|
||||
// 连接完成且出现容器的标题,且没有执行过postAttach命令
|
||||
if (this.connectStatus && compactOutput.includes(this.workdir) && !this.postAttachCommandStatus){
|
||||
console.log('[Xterm] Detected workdir in output, executing postAttachCommand');
|
||||
for (let i = 1; i < this.postAttachCommand.length; i++){
|
||||
this.sendData(this.postAttachCommand[i]+'\n');
|
||||
}
|
||||
@@ -495,12 +521,10 @@ export class Xterm {
|
||||
}
|
||||
break;
|
||||
case Command.SET_WINDOW_TITLE:
|
||||
console.log('SET_WINDOW_TITLESET_WINDOW_TITLE');
|
||||
this.title = textDecoder.decode(data);
|
||||
document.title = this.title;
|
||||
break;
|
||||
case Command.SET_PREFERENCES:
|
||||
console.log('SET_PREFERENCESSET_PREFERENCESSET_PREFERENCES');
|
||||
this.applyPreferences({
|
||||
...this.options.clientOptions,
|
||||
...JSON.parse(textDecoder.decode(data)),
|
||||
|
||||
Reference in New Issue
Block a user