Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ddd46f191 | ||
|
|
2bd8d90bef | ||
|
|
c14df5f893 | ||
|
|
c7bcb552c5 | ||
|
|
2697717620 | ||
|
|
44f2244f32 | ||
|
|
ea1a4c4a52 | ||
|
|
086096dd3e | ||
|
|
a9f3ba5ade | ||
|
|
91ca461202 |
7
.github/workflows/backend.yml
vendored
7
.github/workflows/backend.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
target: [i686, x86_64, arm, armhf, aarch64, mips, mipsel, mips64, mips64el, s390x, win32]
|
target: [i686, x86_64, arm, armhf, aarch64, mips, mipsel, mips64, mips64el, s390x, win32]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: https://devstar.cn/actions/checkout@v4
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -32,8 +32,3 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
BUILD_TARGET: ${{ matrix.target }}
|
BUILD_TARGET: ${{ matrix.target }}
|
||||||
run: ./scripts/cross-build.sh
|
run: ./scripts/cross-build.sh
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ttyd.${{ matrix.target }}
|
|
||||||
path: build/ttyd*
|
|
||||||
|
|
||||||
|
|||||||
27
.github/workflows/docker.yml
vendored
27
.github/workflows/docker.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: https://devstar.cn/actions/checkout@v4
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -25,32 +25,37 @@ jobs:
|
|||||||
[ "$arch" = "armv7" ] && arch="arm"
|
[ "$arch" = "armv7" ] && arch="arm"
|
||||||
mkdir -p dist/$arch && cp build/ttyd dist/$arch/ttyd
|
mkdir -p dist/$arch && cp build/ttyd dist/$arch/ttyd
|
||||||
done
|
done
|
||||||
- uses: docker/setup-qemu-action@v3
|
- uses: https://devstar.cn/alexios/setup-qemu-action@v3
|
||||||
- uses: docker/setup-buildx-action@v3
|
- uses: https://devstar.cn/alexios/setup-buildx-action@v3
|
||||||
- uses: docker/login-action@v3
|
- uses: https://devstar.cn/alexios/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||||
- uses: docker/login-action@v3
|
- uses: alexios/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Determine docker tags
|
- name: Determine docker tags
|
||||||
id: docker_tag
|
id: docker_tag
|
||||||
|
env:
|
||||||
|
DOCKER_USER: ${{ vars.DOCKER_HUB_USERNAME }}
|
||||||
|
PROJECT: ${{ vars.PROJECT_NAME }}
|
||||||
run: |
|
run: |
|
||||||
|
FULL_IMAGE_NAME="${DOCKER_USER}/${PROJECT}"
|
||||||
|
|
||||||
case $GITHUB_REF in
|
case $GITHUB_REF in
|
||||||
refs/tags/*)
|
refs/tags/*)
|
||||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||||
echo "DOCKER_TAG=tsl0922/ttyd:${TAG_NAME}" >> $GITHUB_ENV
|
echo "DOCKER_TAG=${FULL_IMAGE_NAME}:${TAG_NAME}" >> $GITHUB_ENV
|
||||||
echo "ALPINE_TAG=tsl0922/ttyd:${TAG_NAME}-alpine" >> $GITHUB_ENV
|
echo "ALPINE_TAG=${FULL_IMAGE_NAME}:${TAG_NAME}-alpine" >> $GITHUB_ENV
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "DOCKER_TAG=tsl0922/ttyd:latest" >> $GITHUB_ENV
|
echo "DOCKER_TAG=${FULL_IMAGE_NAME}:latest" >> $GITHUB_ENV
|
||||||
echo "ALPINE_TAG=tsl0922/ttyd:alpine" >> $GITHUB_ENV
|
echo "ALPINE_TAG=${FULL_IMAGE_NAME}:alpine" >> $GITHUB_ENV
|
||||||
esac
|
esac
|
||||||
- name: build/push docker image
|
- name: build/push docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: https://devstar.cn/alexios/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
@@ -60,7 +65,7 @@ jobs:
|
|||||||
${{ env.DOCKER_TAG }}
|
${{ env.DOCKER_TAG }}
|
||||||
ghcr.io/${{ env.DOCKER_TAG }}
|
ghcr.io/${{ env.DOCKER_TAG }}
|
||||||
- name: build/push docker image (alpine)
|
- name: build/push docker image (alpine)
|
||||||
uses: docker/build-push-action@v6
|
uses: https://devstar.cn/alexios/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile.alpine
|
file: ./Dockerfile.alpine
|
||||||
|
|||||||
7
.github/workflows/frontend.yml
vendored
7
.github/workflows/frontend.yml
vendored
@@ -14,15 +14,16 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: https://devstar.cn/actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: https://devstar.cn/actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
- name: Run yarn install, check and build
|
- name: Run yarn install, check and build
|
||||||
run: |
|
run: |
|
||||||
corepack enable
|
corepack enable
|
||||||
corepack prepare yarn@stable --activate
|
corepack prepare yarn@stable --activate
|
||||||
yarn install
|
yarn install --no-immutable
|
||||||
|
yarn add -D eslint-plugin-n
|
||||||
yarn run check
|
yarn run check
|
||||||
yarn run build
|
yarn run build
|
||||||
working-directory: html
|
working-directory: html
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ export class Xterm {
|
|||||||
private textDecoder = new TextDecoder();
|
private textDecoder = new TextDecoder();
|
||||||
private written = 0;
|
private written = 0;
|
||||||
private pending = 0;
|
private pending = 0;
|
||||||
private attachWatchdogTick = 0;
|
|
||||||
|
|
||||||
private terminal: Terminal;
|
private terminal: Terminal;
|
||||||
private fitAddon = new FitAddon();
|
private fitAddon = new FitAddon();
|
||||||
@@ -113,9 +112,6 @@ export class Xterm {
|
|||||||
private containerStatus = "";
|
private containerStatus = "";
|
||||||
private attachCommandSent = false;
|
private attachCommandSent = false;
|
||||||
private attachCommandSentAt?: number;
|
private attachCommandSentAt?: number;
|
||||||
private ptyOutputReceived = false;
|
|
||||||
private attachWatchdogId?: NodeJS.Timeout;
|
|
||||||
private commandLoadInFlight = false;
|
|
||||||
constructor(
|
constructor(
|
||||||
private options: XtermOptions,
|
private options: XtermOptions,
|
||||||
private sendCb: () => void
|
private sendCb: () => void
|
||||||
@@ -126,10 +122,6 @@ export class Xterm {
|
|||||||
d.dispose();
|
d.dispose();
|
||||||
}
|
}
|
||||||
this.disposables.length = 0;
|
this.disposables.length = 0;
|
||||||
if (this.attachWatchdogId) {
|
|
||||||
clearInterval(this.attachWatchdogId);
|
|
||||||
this.attachWatchdogId = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
@@ -284,8 +276,6 @@ export class Xterm {
|
|||||||
@bind
|
@bind
|
||||||
public connect() {
|
public connect() {
|
||||||
this.socket = new WebSocket(this.options.wsUrl, ['tty']);
|
this.socket = new WebSocket(this.options.wsUrl, ['tty']);
|
||||||
this.ptyOutputReceived = false;
|
|
||||||
this.attachWatchdogTick = 0;
|
|
||||||
const { socket, register } = this;
|
const { socket, register } = this;
|
||||||
|
|
||||||
socket.binaryType = 'arraybuffer';
|
socket.binaryType = 'arraybuffer';
|
||||||
@@ -293,31 +283,11 @@ export class Xterm {
|
|||||||
register(addEventListener(socket, 'message', this.onSocketData as EventListener));
|
register(addEventListener(socket, 'message', this.onSocketData as EventListener));
|
||||||
register(addEventListener(socket, 'close', this.onSocketClose as EventListener));
|
register(addEventListener(socket, 'close', this.onSocketClose as EventListener));
|
||||||
register(addEventListener(socket, 'error', () => (this.doReconnect = false)));
|
register(addEventListener(socket, 'error', () => (this.doReconnect = false)));
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@bind
|
|
||||||
private startAttachWatchdog() {
|
|
||||||
if (this.attachWatchdogId) return;
|
|
||||||
this.attachWatchdogId = setInterval(() => {
|
|
||||||
this.attachWatchdogTick++;
|
|
||||||
if (this.connectStatus) return;
|
|
||||||
this.tryExecuteAttachCommand();
|
|
||||||
if (!this.connectStatus) this.loadCommandOnce();
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@bind
|
|
||||||
private stopAttachWatchdog() {
|
|
||||||
if (!this.attachWatchdogId) return;
|
|
||||||
clearInterval(this.attachWatchdogId);
|
|
||||||
this.attachWatchdogId = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
private onSocketOpen() {
|
private onSocketOpen() {
|
||||||
console.log('[webTerminal] WebSocket opened, containerStatus:', this.containerStatus, 'connectStatus:', this.connectStatus, 'attachCommandSent:', this.attachCommandSent);
|
console.log('[webTerminal] WebSocket opened, containerStatus:', this.containerStatus, 'connectStatus:', this.connectStatus, 'attachCommandSent:', this.attachCommandSent);
|
||||||
console.log('[webTerminal] onSocketOpen - postAttachCommand:', this.postAttachCommand?.length || 0, 'ptyOutputReceived:', this.ptyOutputReceived, 'commandLoadInFlight:', this.commandLoadInFlight);
|
|
||||||
|
|
||||||
const { textEncoder, terminal, overlayAddon } = this;
|
const { textEncoder, terminal, overlayAddon } = this;
|
||||||
const msg = JSON.stringify({ AuthToken: this.token, columns: terminal.cols, rows: terminal.rows });
|
const msg = JSON.stringify({ AuthToken: this.token, columns: terminal.cols, rows: terminal.rows });
|
||||||
@@ -340,10 +310,6 @@ export class Xterm {
|
|||||||
this.doReconnect = this.reconnect;
|
this.doReconnect = this.reconnect;
|
||||||
this.initListeners();
|
this.initListeners();
|
||||||
terminal.focus();
|
terminal.focus();
|
||||||
|
|
||||||
// Check if can execute pending command
|
|
||||||
console.log('[webTerminal] onSocketOpen - calling tryExecuteAttachCommand()');
|
|
||||||
this.tryExecuteAttachCommand();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
@@ -374,45 +340,6 @@ export class Xterm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
|
||||||
private tryExecuteAttachCommand() {
|
|
||||||
if (this.connectStatus) {
|
|
||||||
console.log('[Xterm] tryExecuteAttachCommand: already connected');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.postAttachCommand || this.postAttachCommand.length === 0) {
|
|
||||||
console.log('[Xterm] tryExecuteAttachCommand: no command available');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.socket?.readyState !== WebSocket.OPEN) {
|
|
||||||
console.log('[Xterm] tryExecuteAttachCommand: WebSocket not ready, state:', this.socket?.readyState);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.ptyOutputReceived) {
|
|
||||||
console.log('[Xterm] tryExecuteAttachCommand: ttyd not ready yet (waiting for first output)');
|
|
||||||
return; // Wait for TTY readiness confirm via output
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果已发送没连上,允许超时后重发
|
|
||||||
const shouldResend =
|
|
||||||
this.attachCommandSent &&
|
|
||||||
this.attachCommandSentAt !== undefined &&
|
|
||||||
Date.now() - this.attachCommandSentAt > 5000;
|
|
||||||
if (this.attachCommandSent && !shouldResend) {
|
|
||||||
console.log('[Xterm] tryExecuteAttachCommand: command already sent, not resending yet');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cmd = this.postAttachCommand[0];
|
|
||||||
if (cmd) {
|
|
||||||
console.log('[Xterm] ✅ All conditions met, executing attach command...');
|
|
||||||
this.sendData(cmd + "\n");
|
|
||||||
this.attachCommandSent = true;
|
|
||||||
this.attachCommandSentAt = Date.now();
|
|
||||||
console.log('[Xterm] Command sent at:', new Date(this.attachCommandSentAt).toISOString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 URL 查询参数
|
* 获取 URL 查询参数
|
||||||
*/
|
*/
|
||||||
@@ -436,12 +363,6 @@ export class Xterm {
|
|||||||
*/
|
*/
|
||||||
@bind
|
@bind
|
||||||
public loadCommandOnce() {
|
public loadCommandOnce() {
|
||||||
if (this.commandLoadInFlight) {
|
|
||||||
console.log('[Xterm] loadCommandOnce: command load already in flight, skipping');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log('[Xterm] loadCommandOnce: starting command load...');
|
|
||||||
this.commandLoadInFlight = true;
|
|
||||||
this.loadCommandWithRetry(0);
|
this.loadCommandWithRetry(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,7 +375,6 @@ export class Xterm {
|
|||||||
const maxRetries = 5;
|
const maxRetries = 5;
|
||||||
const { params, baseUrl } = this.getUrlParams();
|
const { params, baseUrl } = this.getUrlParams();
|
||||||
|
|
||||||
console.log(`[Xterm] loadCommandWithRetry: attempt ${retryCount + 1}/${maxRetries}, fetching command from ${baseUrl}/devcontainer/command?${params}`);
|
|
||||||
fetch(`${baseUrl}/devcontainer/command?${params}`)
|
fetch(`${baseUrl}/devcontainer/command?${params}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -473,10 +393,14 @@ export class Xterm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 执行连接容器的命令(只执行一次)
|
// 执行连接容器的命令(只执行一次)
|
||||||
console.log('[Xterm] Command loaded successfully, attempting to execute...');
|
const parts = data.command.split('\n');
|
||||||
this.postAttachCommand = data.command.split('\n');
|
if (parts[0] && !this.connectStatus) {
|
||||||
this.tryExecuteAttachCommand();
|
console.log('[Xterm] Successfully loaded connection command, executing...');
|
||||||
this.commandLoadInFlight = false;
|
this.sendData(parts[0]+"\n");
|
||||||
|
this.attachCommandSent = true;
|
||||||
|
this.attachCommandSentAt = Date.now();
|
||||||
|
}
|
||||||
|
this.postAttachCommand = parts;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(`[Xterm] Error loading command (attempt ${retryCount + 1}/${maxRetries}):`, error);
|
console.error(`[Xterm] Error loading command (attempt ${retryCount + 1}/${maxRetries}):`, error);
|
||||||
@@ -491,26 +415,12 @@ export class Xterm {
|
|||||||
} else {
|
} else {
|
||||||
console.error('[Xterm] Failed to load command after all retries');
|
console.error('[Xterm] Failed to load command after all retries');
|
||||||
// 可以在这里显示错误提示给用户
|
// 可以在这里显示错误提示给用户
|
||||||
this.commandLoadInFlight = false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@bind
|
@bind
|
||||||
public changeContainerStatus(v: string){
|
public changeContainerStatus(v: string){
|
||||||
const oldStatus = this.containerStatus;
|
|
||||||
this.containerStatus = v;
|
this.containerStatus = v;
|
||||||
|
|
||||||
const { options } = this.getUrlParams();
|
|
||||||
if (options.get('type') !== 'docker') return;
|
|
||||||
|
|
||||||
const statusNum = parseInt(v);
|
|
||||||
const oldStatusNum = oldStatus ? parseInt(oldStatus) : -1;
|
|
||||||
|
|
||||||
// 检测到状态 9(已停止):启动 watchdog,等待容器启动
|
|
||||||
if (statusNum === 9 && !this.connectStatus) {
|
|
||||||
console.log('[Xterm] Container is stopped (status 9), starting attach watchdog to wait for startup');
|
|
||||||
this.startAttachWatchdog();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
@@ -555,85 +465,48 @@ export class Xterm {
|
|||||||
const data = rawData.slice(1);
|
const data = rawData.slice(1);
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case Command.OUTPUT:
|
case Command.OUTPUT:
|
||||||
if (!this.ptyOutputReceived) {
|
|
||||||
this.ptyOutputReceived = true;
|
|
||||||
console.log('[Xterm] OUTPUT: ttyd is now ready (received first output), attempting to execute attach command');
|
|
||||||
this.tryExecuteAttachCommand();
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodedData = textDecoder.decode(data);
|
const decodedData = textDecoder.decode(data);
|
||||||
const pure = this.stripAnsi(decodedData);
|
console.log('[ttyd] output:', decodedData);
|
||||||
const compactOutput = decodedData.replace(/\s/g, '');
|
const compactOutput = decodedData.replace(/\s/g, '');
|
||||||
const { options } = this.getUrlParams();
|
const { options } = this.getUrlParams();
|
||||||
if (options.get('type') === 'docker') {
|
if (options.get('type') === 'docker') {
|
||||||
// 保存 host的标题
|
// 保存host的标题
|
||||||
const pureContent = this.stripAnsi(decodedData).trim();
|
if (this.hostTitle === ''){
|
||||||
if (!this.connectStatus && !this.attachCommandSent && this.hostTitle === '' && pureContent.length > 0) {
|
this.hostTitle = compactOutput;
|
||||||
this.hostTitle = pureContent;
|
|
||||||
}
|
}
|
||||||
|
// 检测是否退出devcontainer,标题等于host的标题
|
||||||
// 检测是否退出 devcontainer:
|
if (this.connectStatus && compactOutput.includes(this.hostTitle)){
|
||||||
if (this.connectStatus && this.hostTitle && pureContent === this.hostTitle) {
|
|
||||||
this.connectStatus = false;
|
this.connectStatus = false;
|
||||||
this.connectionMessageBuffer = '';
|
this.connectionMessageBuffer = '';
|
||||||
this.attachCommandSent = false;
|
this.attachCommandSent = false;
|
||||||
this.attachCommandSentAt = undefined;
|
this.attachCommandSentAt = undefined;
|
||||||
this.postAttachCommandStatus = false;
|
this.postAttachCommandStatus = false;
|
||||||
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
||||||
this.ptyOutputReceived = true;
|
|
||||||
} else {
|
|
||||||
this.ptyOutputReceived = false;
|
|
||||||
}
|
|
||||||
this.startAttachWatchdog();
|
|
||||||
}
|
}
|
||||||
|
// 检测连接完成:监听 "Successfully connected to the devcontainer" 消息
|
||||||
if (this.connectStatus) {
|
// 这条消息是由连接命令中的 echo "$WEB_TERMINAL_HELLO" 输出的
|
||||||
try {
|
if (!this.connectStatus) {
|
||||||
this.writeFunc(data);
|
const sanitizedOutput = this.stripAnsi(decodedData).replace(/\r/g, '\n');
|
||||||
} catch (e) {
|
const combinedOutput = this.connectionMessageBuffer + sanitizedOutput;
|
||||||
console.error('[Xterm] writeFunc error:', e);
|
const segments = combinedOutput.split(/\n/);
|
||||||
}
|
this.connectionMessageBuffer = segments.pop() ?? '';
|
||||||
} else {
|
const hasSuccessLine = segments.some(line => line.trim() === 'Successfully connected to the devcontainer');
|
||||||
// 未连接状态:缓冲所有输出
|
if (hasSuccessLine) {
|
||||||
this.connectionMessageBuffer += decodedData;
|
|
||||||
|
|
||||||
// docker exec 失败时(容器不存在/未运行),只重置状态,让 watchdog 定时重试
|
|
||||||
const lower = pure.toLowerCase();
|
|
||||||
const isDockerExecError =
|
|
||||||
lower.includes('error response from daemon') &&
|
|
||||||
(lower.includes('is not running') || lower.includes('no such container') || lower.includes('cannot connect'));
|
|
||||||
if (isDockerExecError) {
|
|
||||||
this.attachCommandSent = false;
|
|
||||||
this.attachCommandSentAt = undefined;
|
|
||||||
this.connectionMessageBuffer = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const successMarker = 'Successfully connected to the devcontainer';
|
|
||||||
// 尝试在 buffer 中查找成功标记
|
|
||||||
const markerIndex = this.connectionMessageBuffer.indexOf(successMarker);
|
|
||||||
|
|
||||||
if (markerIndex !== -1) {
|
|
||||||
console.log('[Xterm] ✅ Connection established! Found success marker in buffer');
|
|
||||||
this.connectStatus = true;
|
this.connectStatus = true;
|
||||||
this.terminal.options.disableStdin = false;
|
|
||||||
this.stopAttachWatchdog();
|
|
||||||
|
|
||||||
const validOutput = this.connectionMessageBuffer.substring(markerIndex);
|
|
||||||
this.writeData(validOutput);
|
|
||||||
this.connectionMessageBuffer = '';
|
this.connectionMessageBuffer = '';
|
||||||
} else {
|
this.attachCommandSentAt = undefined;
|
||||||
// 调试:如果命令已发送但还没连接成功,检查 buffer 内容
|
console.log('[Xterm] Connection established, enabling terminal input');
|
||||||
if (this.attachCommandSent && !this.connectStatus && this.connectionMessageBuffer.length > 0) {
|
// 确保终端输入已启用
|
||||||
const bufferPreview = this.connectionMessageBuffer.substring(0, 200).replace(/\n/g, '\\n').replace(/\r/g, '\\r');
|
this.terminal.options.disableStdin = false;
|
||||||
console.log('[Xterm] Waiting for connection success... buffer length:', this.connectionMessageBuffer.length, 'preview:', bufferPreview);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.connectionMessageBuffer.length > 20000) {
|
|
||||||
this.connectionMessageBuffer = this.connectionMessageBuffer.slice(-5000);
|
|
||||||
}
|
}
|
||||||
|
// 连接完成之前,过滤掉 docker exec 命令的标题输出(ANSI 码和 docker-H 开头的输出)
|
||||||
|
if (
|
||||||
|
!(this.connectStatus === false &&
|
||||||
|
(textDecoder.decode(data).includes('\x1b') ||
|
||||||
|
textDecoder.decode(data).replace(/\s/g, '').includes('docker-H')))
|
||||||
|
){
|
||||||
|
this.writeFunc(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 连接完成且出现容器的标题,且没有执行过postAttach命令
|
// 连接完成且出现容器的标题,且没有执行过postAttach命令
|
||||||
if (this.connectStatus && compactOutput.includes(this.workdir) && !this.postAttachCommandStatus){
|
if (this.connectStatus && compactOutput.includes(this.workdir) && !this.postAttachCommandStatus){
|
||||||
console.log('[Xterm] Detected workdir in output, executing postAttachCommand');
|
console.log('[Xterm] Detected workdir in output, executing postAttachCommand');
|
||||||
@@ -652,12 +525,6 @@ export class Xterm {
|
|||||||
document.title = this.title;
|
document.title = this.title;
|
||||||
break;
|
break;
|
||||||
case Command.SET_PREFERENCES:
|
case Command.SET_PREFERENCES:
|
||||||
console.log('[Xterm] Received SET_PREFERENCES, ptyOutputReceived:', this.ptyOutputReceived);
|
|
||||||
if (!this.ptyOutputReceived) {
|
|
||||||
this.ptyOutputReceived = true;
|
|
||||||
console.log('[Xterm] SET_PREFERENCES: ttyd is now ready, attempting to execute attach command');
|
|
||||||
this.tryExecuteAttachCommand();
|
|
||||||
}
|
|
||||||
this.applyPreferences({
|
this.applyPreferences({
|
||||||
...this.options.clientOptions,
|
...this.options.clientOptions,
|
||||||
...JSON.parse(textDecoder.decode(data)),
|
...JSON.parse(textDecoder.decode(data)),
|
||||||
@@ -837,12 +704,6 @@ export class Xterm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private stripAnsi(input: string): string {
|
private stripAnsi(input: string): string {
|
||||||
// CSI: ESC [ ... command
|
return input.replace(/\u001B\[[0-9;?]*[ -\/]*[@-~]/g, '').replace(/\u0007/g, '');
|
||||||
// OSC: ESC ] ... (BEL or ESC \)
|
|
||||||
// BEL: \u0007
|
|
||||||
return input
|
|
||||||
.replace(/\u001B\][^\u0007\u001B]*(?:\u0007|\u001B\\)/g, '') // OSC ... BEL or ST
|
|
||||||
.replace(/\u001B\[[0-9;?]*[ -\/]*[@-~]/g, '') // CSI
|
|
||||||
.replace(/\u0007/g, ''); // stray BEL
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,17 @@ MBEDTLS_VERSION="${MBEDTLS_VERSION:-2.28.5}"
|
|||||||
LIBUV_VERSION="${LIBUV_VERSION:-1.44.2}"
|
LIBUV_VERSION="${LIBUV_VERSION:-1.44.2}"
|
||||||
LIBWEBSOCKETS_VERSION="${LIBWEBSOCKETS_VERSION:-4.3.3}"
|
LIBWEBSOCKETS_VERSION="${LIBWEBSOCKETS_VERSION:-4.3.3}"
|
||||||
|
|
||||||
|
BASE_URL="https://devstar.cn/alexios/zip-repo/releases/download"
|
||||||
|
URL_TOOLCHAIN="${BASE_URL}/v1.0/aarch64-linux-musl-cross.tgz"
|
||||||
|
URL_ZLIB="${BASE_URL}/v1.0/zlib-1.3.1.tar.gz"
|
||||||
|
URL_JSONC="${BASE_URL}/v1.0/json-c-0.17.tar.gz"
|
||||||
|
URL_MBEDTLS="${BASE_URL}/v1.0/mbedtls-2.28.5.tar.gz"
|
||||||
|
URL_LIBUV="${BASE_URL}/v1.0/libuv-v1.44.2.tar.gz"
|
||||||
|
URL_LWS="${BASE_URL}/v1.0/libwebsockets-4.3.3.tar.gz"
|
||||||
|
|
||||||
build_zlib() {
|
build_zlib() {
|
||||||
echo "=== Building zlib-${ZLIB_VERSION} (${TARGET})..."
|
echo "=== Building zlib-${ZLIB_VERSION} (${TARGET})..."
|
||||||
curl -fSsLo- "https://zlib.net/zlib-${ZLIB_VERSION}.tar.gz" | tar xz -C "${BUILD_DIR}"
|
curl -kfSsLo- "$URL_ZLIB" | tar xz -C "${BUILD_DIR}"
|
||||||
pushd "${BUILD_DIR}"/zlib-"${ZLIB_VERSION}"
|
pushd "${BUILD_DIR}"/zlib-"${ZLIB_VERSION}"
|
||||||
env CHOST="${TARGET}" ./configure --static --archs="-fPIC" --prefix="${STAGE_DIR}"
|
env CHOST="${TARGET}" ./configure --static --archs="-fPIC" --prefix="${STAGE_DIR}"
|
||||||
make -j"$(nproc)" install
|
make -j"$(nproc)" install
|
||||||
@@ -27,7 +35,7 @@ build_zlib() {
|
|||||||
|
|
||||||
build_json-c() {
|
build_json-c() {
|
||||||
echo "=== Building json-c-${JSON_C_VERSION} (${TARGET})..."
|
echo "=== Building json-c-${JSON_C_VERSION} (${TARGET})..."
|
||||||
curl -fSsLo- "https://s3.amazonaws.com/json-c_releases/releases/json-c-${JSON_C_VERSION}.tar.gz" | tar xz -C "${BUILD_DIR}"
|
curl -kfSsLo- "$URL_JSONC" | tar xz -C "${BUILD_DIR}"
|
||||||
pushd "${BUILD_DIR}/json-c-${JSON_C_VERSION}"
|
pushd "${BUILD_DIR}/json-c-${JSON_C_VERSION}"
|
||||||
rm -rf build && mkdir -p build && cd build
|
rm -rf build && mkdir -p build && cd build
|
||||||
cmake -DCMAKE_TOOLCHAIN_FILE="${BUILD_DIR}/cross-${TARGET}.cmake" \
|
cmake -DCMAKE_TOOLCHAIN_FILE="${BUILD_DIR}/cross-${TARGET}.cmake" \
|
||||||
@@ -43,7 +51,7 @@ build_json-c() {
|
|||||||
|
|
||||||
build_mbedtls() {
|
build_mbedtls() {
|
||||||
echo "=== Building mbedtls-${MBEDTLS_VERSION} (${TARGET})..."
|
echo "=== Building mbedtls-${MBEDTLS_VERSION} (${TARGET})..."
|
||||||
curl -fSsLo- "https://github.com/ARMmbed/mbedtls/archive/v${MBEDTLS_VERSION}.tar.gz" | tar xz -C "${BUILD_DIR}"
|
curl -kfSsLo- "$URL_MBEDTLS" | tar xz -C "${BUILD_DIR}"
|
||||||
pushd "${BUILD_DIR}/mbedtls-${MBEDTLS_VERSION}"
|
pushd "${BUILD_DIR}/mbedtls-${MBEDTLS_VERSION}"
|
||||||
rm -rf build && mkdir -p build && cd build
|
rm -rf build && mkdir -p build && cd build
|
||||||
cmake -DCMAKE_TOOLCHAIN_FILE="${BUILD_DIR}/cross-${TARGET}.cmake" \
|
cmake -DCMAKE_TOOLCHAIN_FILE="${BUILD_DIR}/cross-${TARGET}.cmake" \
|
||||||
@@ -57,7 +65,7 @@ build_mbedtls() {
|
|||||||
|
|
||||||
build_libuv() {
|
build_libuv() {
|
||||||
echo "=== Building libuv-${LIBUV_VERSION} (${TARGET})..."
|
echo "=== Building libuv-${LIBUV_VERSION} (${TARGET})..."
|
||||||
curl -fSsLo- "https://dist.libuv.org/dist/v${LIBUV_VERSION}/libuv-v${LIBUV_VERSION}.tar.gz" | tar xz -C "${BUILD_DIR}"
|
curl -kfSsLo- "$URL_LIBUV" | tar xz -C "${BUILD_DIR}"
|
||||||
pushd "${BUILD_DIR}/libuv-v${LIBUV_VERSION}"
|
pushd "${BUILD_DIR}/libuv-v${LIBUV_VERSION}"
|
||||||
./autogen.sh
|
./autogen.sh
|
||||||
env CFLAGS=-fPIC ./configure --disable-shared --enable-static --prefix="${STAGE_DIR}" --host="${TARGET}"
|
env CFLAGS=-fPIC ./configure --disable-shared --enable-static --prefix="${STAGE_DIR}" --host="${TARGET}"
|
||||||
@@ -83,7 +91,7 @@ EOF
|
|||||||
|
|
||||||
build_libwebsockets() {
|
build_libwebsockets() {
|
||||||
echo "=== Building libwebsockets-${LIBWEBSOCKETS_VERSION} (${TARGET})..."
|
echo "=== Building libwebsockets-${LIBWEBSOCKETS_VERSION} (${TARGET})..."
|
||||||
curl -fSsLo- "https://github.com/warmcat/libwebsockets/archive/v${LIBWEBSOCKETS_VERSION}.tar.gz" | tar xz -C "${BUILD_DIR}"
|
curl -kfSsLo- "$URL_LWS" | tar xz -C "${BUILD_DIR}"
|
||||||
pushd "${BUILD_DIR}/libwebsockets-${LIBWEBSOCKETS_VERSION}"
|
pushd "${BUILD_DIR}/libwebsockets-${LIBWEBSOCKETS_VERSION}"
|
||||||
sed -i 's/ websockets_shared//g' cmake/libwebsockets-config.cmake.in
|
sed -i 's/ websockets_shared//g' cmake/libwebsockets-config.cmake.in
|
||||||
sed -i 's/ OR PC_OPENSSL_FOUND//g' lib/tls/CMakeLists.txt
|
sed -i 's/ OR PC_OPENSSL_FOUND//g' lib/tls/CMakeLists.txt
|
||||||
@@ -134,7 +142,6 @@ build() {
|
|||||||
ALIAS="$2"
|
ALIAS="$2"
|
||||||
STAGE_DIR="${STAGE_ROOT}/${TARGET}"
|
STAGE_DIR="${STAGE_ROOT}/${TARGET}"
|
||||||
BUILD_DIR="${BUILD_ROOT}/${TARGET}"
|
BUILD_DIR="${BUILD_ROOT}/${TARGET}"
|
||||||
MUSL_CC_URL="https://github.com/tsl0922/musl-toolchains/releases/download/2021-11-23"
|
|
||||||
COMPONENTS="1"
|
COMPONENTS="1"
|
||||||
SYSTEM="Linux"
|
SYSTEM="Linux"
|
||||||
|
|
||||||
@@ -144,12 +151,12 @@ build() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "=== Installing toolchain ${ALIAS} (${TARGET})..."
|
echo "=== Installing toolchain ${ALIAS} (${TARGET})..."
|
||||||
|
|
||||||
mkdir -p "${CROSS_ROOT}" && export PATH="${PATH}:${CROSS_ROOT}/bin"
|
mkdir -p "${CROSS_ROOT}" && export PATH="${PATH}:${CROSS_ROOT}/bin"
|
||||||
curl -fSsLo- "${MUSL_CC_URL}/${TARGET}-cross.tgz" | tar xz -C "${CROSS_ROOT}" --strip-components=${COMPONENTS}
|
TOOLCHAIN_FILE="${TARGET}-cross.tgz"
|
||||||
|
DOWNLOAD_URL="${BASE_URL}/v1.0/${TOOLCHAIN_FILE}"
|
||||||
echo "=== Building target ${ALIAS} (${TARGET})..."
|
|
||||||
|
echo "Downloading toolchain: $DOWNLOAD_URL"
|
||||||
|
curl -kfSsLo- "$DOWNLOAD_URL" | tar xz -C "${CROSS_ROOT}" --strip-components=${COMPONENTS}
|
||||||
rm -rf "${STAGE_DIR}" "${BUILD_DIR}"
|
rm -rf "${STAGE_DIR}" "${BUILD_DIR}"
|
||||||
mkdir -p "${STAGE_DIR}" "${BUILD_DIR}"
|
mkdir -p "${STAGE_DIR}" "${BUILD_DIR}"
|
||||||
export PKG_CONFIG_PATH="${STAGE_DIR}/lib/pkgconfig"
|
export PKG_CONFIG_PATH="${STAGE_DIR}/lib/pkgconfig"
|
||||||
|
|||||||
32610
src/html.h
generated
32610
src/html.h
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user