重构开发容器创建过程
This commit is contained in:
@@ -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