From 88a196a77cb2de0b19838feb57c42a667a8fde9a Mon Sep 17 00:00:00 2001 From: cmliu Date: Fri, 31 Jul 2026 20:26:37 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E8=BF=9B=E7=A8=8B?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=99=A8=E4=B8=AD=E7=9A=84=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E9=80=BB=E8=BE=91=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E8=A1=A8=E6=A0=BC=E5=88=97=E9=A1=BA=E5=BA=8F?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E6=9B=B4=E6=96=B0=E4=B8=AD=E6=96=87=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E4=BB=A5=E6=8F=90=E9=AB=98=E5=8F=AF=E8=AF=BB=E6=80=A7?= =?UTF-8?q?=20fix:=20=E9=87=8A=E6=94=BE=E6=8C=82=E8=B5=B7=E7=9A=84?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E7=BB=88=E6=AD=A2=E9=80=9A=E9=81=93=EF=BC=8C?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E9=87=8D=E5=A4=8D=E8=B6=85=E6=97=B6=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E5=86=85=E5=AD=98=E7=B4=AF=E7=A7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/process-manager.ts | 11 ++++++----- src/backend/session.ts | 9 +++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/frontend/src/process-manager.ts b/frontend/src/process-manager.ts index 6f44d5d..0370863 100644 --- a/frontend/src/process-manager.ts +++ b/frontend/src/process-manager.ts @@ -205,9 +205,10 @@ export class ProcessManager { if (killButton) { const pid = Number.parseInt(killButton.dataset.processKillPid ?? '', 10); if (!Number.isSafeInteger(pid) || pid <= 0) return; - const row = killButton.closest('tr'); - const commandCell = row?.children.item(7); - const command = commandCell?.textContent ?? '--'; + // Reuse the command text already stored on the button's title attribute (set in render()). + // Avoids depending on the table's column order via children.item(N), which breaks when + // columns are added or rearranged. + const command = killButton.title || '--'; this.openKillDialog(pid, command); return; } @@ -630,8 +631,8 @@ export class ProcessManager { const fragment = document.createDocumentFragment(); const language = this.getLanguage(); - const killLabelZh = '强制结束'; - const killLabelEn = 'Force terminate'; + const killLabelZh = '结束进程'; + const killLabelEn = 'Terminate process'; for (const process of this.sortedProcesses(snapshot.processes)) { const row = document.createElement('tr'); const values = [ diff --git a/src/backend/session.ts b/src/backend/session.ts index 61a7ac0..8aa2bb6 100644 --- a/src/backend/session.ts +++ b/src/backend/session.ts @@ -1138,6 +1138,15 @@ export class SSHSession { stdout: '', stderr: 'Timed out opening the kill channel', }); + // Release the pending kill entry (with its decoders + stdout/stderr buffers) + // so repeated timeouts cannot accumulate in worker memory. We deliberately + // do NOT delete from this.channels here — keeping the SSH channel registered + // means a late CHANNEL_OPEN_CONFIRMATION still resolves in handleChannel() + // (channel found in this.channels) and falls through to the generic close + // branch instead of throwing "unknown recipient". The resulting + // CHANNEL_CLOSE handler then finds no pending kill and quietly skips + // finalize, so there is no double result-send. + this.pendingProcessKillChannels.delete(expected.channelID); if (expected.channel.isOpen()) void this.sendAuxiliaryChannelClose(expected.channel); }