From 9eced1d0efd57465661b8a1560871b039b2f4f60 Mon Sep 17 00:00:00 2001 From: gggaiitx Date: Tue, 29 Sep 2026 10:36:20 +0800 Subject: [PATCH] =?UTF-8?q?fix(forwards):=20=E7=AB=AF=E5=8F=A3=E6=98=A0?= =?UTF-8?q?=E5=B0=84=E6=B7=BB=E5=8A=A0=E8=A1=A8=E5=8D=95=E6=97=A0=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E6=97=B6=E7=9A=84=E9=9D=99=E9=BB=98=E6=AD=BB=E6=8C=89?= =?UTF-8?q?=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -
是无效属性:sessionId 为空时输入框仍可编辑、仅按钮变灰, 用户填完内容点击无任何反馈;改用合法的
真正禁用控件。 - submitForward 的 if (!sessionId) return 静默返回改为可见提示 (新增 i18n forwards.error.noSession,七语齐全)。 - 提交增加 submitting 忙碌锁:防重复提交、慢 RPC(远程转发等待服务端) 时按钮显示加载态,不再与「无响应」混淆。 - 提交失败时错误文案在弹窗内显示(原先清空后仅向父级冒泡,失败不可见)。 --- frontend/src/components/PortForwardDialog.vue | 100 +++++++++++------- frontend/src/lib/i18n.ts | 14 +-- 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/frontend/src/components/PortForwardDialog.vue b/frontend/src/components/PortForwardDialog.vue index dd921d14..8514ec79 100644 --- a/frontend/src/components/PortForwardDialog.vue +++ b/frontend/src/components/PortForwardDialog.vue @@ -78,6 +78,8 @@ function applyForwardFormError(code: ForwardFormError) { forwardFormMessage.value = code ? t(`forwards.error.${code}`) : ""; } +const submitting = ref(false); + async function submitForward() { const error = validateForwardForm(forwardForm); if (error) { @@ -92,7 +94,14 @@ async function submitForward() { }); return; } - if (!props.sessionId) return; + // 无会话时按钮本身已禁用,这里再兜底一次并给出可见提示,避免静默无响应。 + if (!props.sessionId) { + forwardFormMessage.value = t("forwards.error.noSession"); + return; + } + if (submitting.value) return; + submitting.value = true; + forwardFormMessage.value = ""; try { const payload = await window.dbxPlugin.invoke( "ssh/forward/start", @@ -104,8 +113,11 @@ async function submitForward() { } forwardFormMessage.value = ""; } catch (cause) { - forwardFormMessage.value = ""; + // 既在弹窗内显示错误文案,又向父级冒泡,确保任何失败都有可见反馈。 + forwardFormMessage.value = cause instanceof Error ? cause.message : String(cause); emit("error", cause); + } finally { + submitting.value = false; } } @@ -174,46 +186,54 @@ watch( - -
- - -
-
- - -
+ + +
+
+ + +
+
+ + +
+
+

{{ t("forwards.error.noSession") }}

{{ forwardFormMessage }}

diff --git a/frontend/src/lib/i18n.ts b/frontend/src/lib/i18n.ts index dbe4fcc0..240b1983 100644 --- a/frontend/src/lib/i18n.ts +++ b/frontend/src/lib/i18n.ts @@ -73,7 +73,7 @@ export const messages = { allInterfaces: "All interfaces (0.0.0.0)", loopback: "loopback", state: { starting: "Starting", active: "Active", stopped: "Stopped", error: "Error" }, - error: { targetHost: "Target host must be a valid IPv4, IPv6 or hostname.", listenHost: "Listen host must be a valid IPv4, IPv6 or hostname.", port: "Ports must be integers within 0-65535 (0 = auto).", conflict: "Listen endpoint {route} is already forwarded ({existing})." }, + error: { targetHost: "Target host must be a valid IPv4, IPv6 or hostname.", listenHost: "Listen host must be a valid IPv4, IPv6 or hostname.", port: "Ports must be integers within 0-65535 (0 = auto).", conflict: "Listen endpoint {route} is already forwarded ({existing}).", noSession: "Establish and keep an SSH session before adding a port mapping." }, }, settingsLoadFailed: "Could not load SSH settings. Retry before saving.", alertTriageLoadFailed: "Could not analyze this alert: {error}", @@ -554,7 +554,7 @@ export const messages = { allInterfaces: "Todas las interfaces (0.0.0.0)", loopback: "loopback", state: { starting: "Iniciando", active: "Activa", stopped: "Detenida", error: "Error" }, - error: { targetHost: "El host de destino debe ser IPv4, IPv6 o un nombre de host válido.", listenHost: "El host de escucha debe ser IPv4, IPv6 o un nombre de host válido.", port: "Los puertos deben ser enteros entre 0 y 65535 (0 = auto).", conflict: "El punto de escucha {route} ya está redirigido ({existing})." }, + error: { targetHost: "El host de destino debe ser IPv4, IPv6 o un nombre de host válido.", listenHost: "El host de escucha debe ser IPv4, IPv6 o un nombre de host válido.", port: "Los puertos deben ser enteros entre 0 y 65535 (0 = auto).", conflict: "El punto de escucha {route} ya está redirigido ({existing}).", noSession: "Establezca y mantenga una sesión SSH antes de añadir una asignación de puertos." }, }, settingsLoadFailed: "No se pudo cargar la configuración SSH. Vuelve a intentarlo antes de guardar.", alertTriageLoadFailed: "No se pudo analizar esta alerta: {error}", @@ -1028,7 +1028,7 @@ export const messages = { allInterfaces: "Tutte le interfacce (0.0.0.0)", loopback: "loopback", state: { starting: "Avvio", active: "Attivo", stopped: "Interrotto", error: "Errore" }, - error: { targetHost: "L'host di destinazione deve essere IPv4, IPv6 o un nome host valido.", listenHost: "L'host di ascolto deve essere IPv4, IPv6 o un nome host valido.", port: "Le porte devono essere interi tra 0 e 65535 (0 = auto).", conflict: "L'endpoint di ascolto {route} è già inoltrato ({existing})." }, + error: { targetHost: "L'host di destinazione deve essere IPv4, IPv6 o un nome host valido.", listenHost: "L'host di ascolto deve essere IPv4, IPv6 o un nome host valido.", port: "Le porte devono essere interi tra 0 e 65535 (0 = auto).", conflict: "L'endpoint di ascolto {route} è già inoltrato ({existing}).", noSession: "Stabilire e mantenere una sessione SSH prima di aggiungere una mappatura delle porte." }, }, settingsLoadFailed: "Impossibile caricare le impostazioni SSH. Riprova prima di salvare.", alertTriageLoadFailed: "Impossibile analizzare questo avviso: {error}", @@ -1502,7 +1502,7 @@ export const messages = { allInterfaces: "すべてのインターフェース (0.0.0.0)", loopback: "ループバック", state: { starting: "開始中", active: "有効", stopped: "停止", error: "エラー" }, - error: { targetHost: "転送先ホストは有効な IPv4・IPv6・ホスト名で指定してください。", listenHost: "待ち受けホストは有効な IPv4・IPv6・ホスト名で指定してください。", port: "ポートは 0〜65535 の整数で指定してください(0 = 自動)。", conflict: "待ち受けエンドポイント {route} は既に転送に使われています({existing})。" }, + error: { targetHost: "転送先ホストは有効な IPv4・IPv6・ホスト名で指定してください。", listenHost: "待ち受けホストは有効な IPv4・IPv6・ホスト名で指定してください。", port: "ポートは 0〜65535 の整数で指定してください(0 = 自動)。", conflict: "待ち受けエンドポイント {route} は既に転送に使われています({existing})。", noSession: "ポート転送を追加する前に、SSH セッションを確立して保持してください。" }, }, settingsLoadFailed: "SSH 設定を読み込めませんでした。保存する前に再試行してください。", alertTriageLoadFailed: "このアラートを分析できませんでした:{error}", @@ -1976,7 +1976,7 @@ export const messages = { allInterfaces: "Todas as interfaces (0.0.0.0)", loopback: "loopback", state: { starting: "Iniciando", active: "Ativo", stopped: "Parado", error: "Erro" }, - error: { targetHost: "O host de destino deve ser IPv4, IPv6 ou um nome de host válido.", listenHost: "O host de escuta deve ser IPv4, IPv6 ou um nome de host válido.", port: "As portas devem ser inteiros entre 0 e 65535 (0 = automático).", conflict: "O ponto de escuta {route} já está encaminhado ({existing})." }, + error: { targetHost: "O host de destino deve ser IPv4, IPv6 ou um nome de host válido.", listenHost: "O host de escuta deve ser IPv4, IPv6 ou um nome de host válido.", port: "As portas devem ser inteiros entre 0 e 65535 (0 = automático).", conflict: "O ponto de escuta {route} já está encaminhado ({existing}).", noSession: "Estabeleça e mantenha uma sessão SSH antes de adicionar um mapeamento de porta." }, }, settingsLoadFailed: "Não foi possível carregar as configurações SSH. Tente novamente antes de salvar.", alertTriageLoadFailed: "Não foi possível analisar este alerta: {error}", @@ -2450,7 +2450,7 @@ export const messages = { allInterfaces: "所有接口 (0.0.0.0)", loopback: "回环", state: { starting: "启动中", active: "运行中", stopped: "已停止", error: "错误" }, - error: { targetHost: "目标地址必须是合法的 IPv4、IPv6 或主机名。", listenHost: "监听地址必须是合法的 IPv4、IPv6 或主机名。", port: "端口必须是 0-65535 的整数(0 = 自动)。", conflict: "监听端点 {route} 已有映射({existing})。" }, + error: { targetHost: "目标地址必须是合法的 IPv4、IPv6 或主机名。", listenHost: "监听地址必须是合法的 IPv4、IPv6 或主机名。", port: "端口必须是 0-65535 的整数(0 = 自动)。", conflict: "监听端点 {route} 已有映射({existing})。", noSession: "请先建立并保持 SSH 会话,再添加端口映射。" }, }, settingsLoadFailed: "SSH 设置加载失败,请重试后再保存。", alertTriageLoadFailed: "告警分析失败:{error}", @@ -2931,7 +2931,7 @@ export const messages = { allInterfaces: "所有介面 (0.0.0.0)", loopback: "回環", state: { starting: "啟動中", active: "執行中", stopped: "已停止", error: "錯誤" }, - error: { targetHost: "目標位址必須是合法的 IPv4、IPv6 或主機名稱。", listenHost: "監聽位址必須是合法的 IPv4、IPv6 或主機名稱。", port: "連接埠必須是 0-65535 的整數(0 = 自動)。", conflict: "監聽端點 {route} 已有映射({existing})。" }, + error: { targetHost: "目標位址必須是合法的 IPv4、IPv6 或主機名稱。", listenHost: "監聽位址必須是合法的 IPv4、IPv6 或主機名稱。", port: "連接埠必須是 0-65535 的整數(0 = 自動)。", conflict: "監聽端點 {route} 已有映射({existing})。", noSession: "請先建立並保持 SSH 連線,再新增連接埠映射。" }, }, settingsLoadFailed: "SSH 設定載入失敗,請重試後再儲存。", alertTriageLoadFailed: "告警分析失敗:{error}",