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( -
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}",