From 3a30f69b916277ba8593c2abd2c57edd211a9d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=98=AD?= Date: Wed, 4 Nov 2026 09:54:19 +0800 Subject: [PATCH] fix(mcp): unify website binding error feedback and fix status refresh and retries (#14290) --- agent/app/service/mcp_website_binding.go | 116 ++++++++--- core/app/service/mcp_website.go | 1 + frontend/src/api/index.ts | 49 +++-- frontend/src/api/modules/panel-mcp.ts | 9 +- frontend/src/api/modules/website.ts | 6 +- frontend/src/lang/modules/en.ts | 1 + frontend/src/lang/modules/es-es.ts | 1 + frontend/src/lang/modules/fa.ts | 1 + frontend/src/lang/modules/ja.ts | 1 + frontend/src/lang/modules/ko.ts | 1 + frontend/src/lang/modules/lo.ts | 1 + frontend/src/lang/modules/ms.ts | 1 + frontend/src/lang/modules/pt-br.ts | 1 + frontend/src/lang/modules/ru.ts | 1 + frontend/src/lang/modules/tr.ts | 1 + frontend/src/lang/modules/zh-Hant.ts | 1 + frontend/src/lang/modules/zh.ts | 1 + .../views/ai/mcp/panel/website-binding.vue | 193 ++++++++++-------- 18 files changed, 253 insertions(+), 133 deletions(-) diff --git a/agent/app/service/mcp_website_binding.go b/agent/app/service/mcp_website_binding.go index c750bc98c..ae10ab3e2 100644 --- a/agent/app/service/mcp_website_binding.go +++ b/agent/app/service/mcp_website_binding.go @@ -666,15 +666,59 @@ func (s *MCPWebsiteBindingService) matchesInitialSite(req request.MCPWebsiteBind if err != nil || (site.Protocol == constant.ProtocolHTTPS) != req.EnableSSL || site.WebsiteSSLID != req.SSLID { return false } - if port == 0 { + explicitPort := port != 0 + if !explicitPort || req.EnableSSL { installed, installErr := s.openresty() if installErr != nil { return false } - port = installed.HttpPort + if !explicitPort { + port = installed.HttpPort + } + if req.EnableSSL && port == installed.HttpPort { + for _, domain := range site.Domains { + if domain.Domain == host && domain.Port == installed.HttpPort && !domain.SSL { + return true // Complete an interrupted bind against its existing HTTP base. + } + } + } } for _, domain := range site.Domains { - if domain.Domain == host && domain.Port == port && !domain.SSL { + if domain.Domain == host && domain.Port == port && domain.SSL == (req.EnableSSL && explicitPort) { + return true + } + } + return false +} + +func mcpWebsiteListenConflict(candidate model.Website, installed model.AppInstall) error { + if err := checkWebsiteListenConflict(candidate, installed.HttpPort, installed.HttpsPort); err != nil { + return err + } + sites, err := websiteRepo.List() + if err != nil { + return err + } + ports := websiteListenPorts(candidate, installed.HttpPort, installed.HttpsPort) + isTLS := func(site model.Website, port int) bool { + return site.Protocol == constant.ProtocolHTTPS && slices.Contains(buildWebsiteTLSPlan(site.Domains, installed.HttpPort, installed.HttpsPort).httpsPorts, port) + } + for _, site := range sites { + if site.ID == candidate.ID || site.Type == constant.Stream { + continue + } + for port := range websiteListenPorts(site, installed.HttpPort, installed.HttpsPort) { + if _, exists := ports[port]; exists && isTLS(candidate, port) != isTLS(site, port) { + return buserr.WithName("ErrDomainIsUsed", site.PrimaryDomain) + } + } + } + return nil +} + +func mcpWebsiteTLSUsesHTTPPort(site model.Website, installed model.AppInstall) bool { + for _, domain := range site.Domains { + if domain.SSL && domain.Port == installed.HttpPort { return true } } @@ -835,6 +879,14 @@ func (s *MCPWebsiteBindingService) Handle(ctx context.Context, req request.MCPWe if req.DeleteWebsite { return s.missingDeletedWebsite(req) } + if req.Operation == "status" && req.WebsiteID == 0 { + partial := model.Website{Alias: mcpWebsiteAlias(req.BindingID)} + for _, location := range []string{GetSitePath(partial, SiteDir), GetSitePath(partial, SiteConf), mcpWebsitePrivatePath(partial, mcpWebsiteOwnerName), mcpWebsitePrivatePath(partial, mcpWebsiteProxyName)} { + if _, err := os.Lstat(location); !os.IsNotExist(err) { + return response.MCPWebsiteBinding{State: "unavailable", BindingID: req.BindingID, RequestID: req.RequestID, Reason: "website_missing"} + } + } + } if req.WebsiteID == 0 || req.Operation == "unbind" { return response.MCPWebsiteBinding{State: "unbound", BindingID: req.BindingID, RequestID: req.RequestID} } @@ -843,6 +895,12 @@ func (s *MCPWebsiteBindingService) Handle(ctx context.Context, req request.MCPWe if req.Operation != "bind" { return mcpWebsiteFailure("website_missing", true, true) } + if req.WebsiteID != 0 { + return mcpWebsiteFailure("website_missing", true, true) + } + if _, ownerErr := os.Lstat(mcpWebsitePrivatePath(model.Website{Alias: mcpWebsiteAlias(req.BindingID)}, mcpWebsiteOwnerName)); !os.IsNotExist(ownerErr) { + return mcpWebsiteFailure("website_ownership_changed", false, true) + } installed, installErr := s.openresty() if installErr != nil || installed.ID == 0 || installed.Status != constant.StatusRunning { return mcpWebsiteFailure("openresty_unavailable", true, false) @@ -852,17 +910,19 @@ func (s *MCPWebsiteBindingService) Handle(ctx context.Context, req request.MCPWe return mcpWebsiteFailure("website_group_unavailable", true, false) } domain, port, _ := mcpWebsiteDomain(req.Domain) + explicitPort := port != 0 if port == 0 { port = installed.HttpPort } - if req.EnableSSL && port != installed.HttpPort { - return mcpWebsiteFailure("http_port_requires_default", true, false) - } - candidate := model.Website{Protocol: constant.ProtocolHTTP, Domains: []model.WebsiteDomain{{Domain: domain, Port: port}}} + domainSSL := req.EnableSSL && explicitPort + candidate := model.Website{Protocol: constant.ProtocolHTTP, Domains: []model.WebsiteDomain{{Domain: domain, Port: port, SSL: domainSSL}}} if req.EnableSSL { candidate.Protocol = constant.ProtocolHTTPS + if mcpWebsiteTLSUsesHTTPPort(candidate, installed) { + return mcpWebsiteFailure("invalid_https_port", true, false) + } } - if conflictErr := checkWebsiteListenConflict(candidate, installed.HttpPort, installed.HttpsPort); conflictErr != nil { + if conflictErr := mcpWebsiteListenConflict(candidate, installed); conflictErr != nil { var business buserr.BusinessError if errors.As(conflictErr, &business) && business.Msg == "ErrDomainIsUsed" { return mcpWebsiteFailure("domain_conflict", true, true) @@ -870,7 +930,7 @@ func (s *MCPWebsiteBindingService) Handle(ctx context.Context, req request.MCPWe return mcpWebsiteFailure("website_unavailable", false, false) } create := request.WebsiteCreate{Alias: mcpWebsiteAlias(req.BindingID), Type: constant.Static, Remark: mcpWebsiteRemark(req), WebsiteGroupID: group.ID, - Domains: []request.WebsiteDomain{{Domain: domain, Port: port}}, SSLConfig: request.SSLConfig{EnableSSL: req.EnableSSL, WebsiteSSLID: req.SSLID}} + Domains: []request.WebsiteDomain{{Domain: domain, Port: port, SSL: domainSSL}}, SSLConfig: request.SSLConfig{EnableSSL: req.EnableSSL, WebsiteSSLID: req.SSLID}} previousMutation := mutationStarted mutationStarted = true if createErr := s.createWebsite(create); createErr != nil { @@ -966,6 +1026,27 @@ func (s *MCPWebsiteBindingService) Handle(ctx context.Context, req request.MCPWe if req.Operation == "bind" && owner.BindHash != mcpWebsiteBindHash(req) { return mcpWebsiteFailure("binding_request_changed", true, true) } + if req.Operation == "update" && (site.Protocol == constant.ProtocolHTTPS) != req.EnableSSL { + installed, installErr := s.openresty() + if installErr != nil || installed.ID == 0 || installed.Status != constant.StatusRunning { + return mcpWebsiteFailure("openresty_unavailable", true, false) + } + candidate := site + candidate.Protocol = constant.ProtocolHTTP + if req.EnableSSL { + candidate.Protocol = constant.ProtocolHTTPS + if mcpWebsiteTLSUsesHTTPPort(candidate, installed) { + return mcpWebsiteFailure("invalid_https_port", true, false) + } + } + if conflictErr := mcpWebsiteListenConflict(candidate, installed); conflictErr != nil { + var business buserr.BusinessError + if errors.As(conflictErr, &business) && business.Msg == "ErrDomainIsUsed" { + return mcpWebsiteFailure("domain_conflict", true, true) + } + return mcpWebsiteFailure("website_unavailable", false, false) + } + } if owner.ProxyHash != "" && owner.Operation != "unbind" && req.Operation != "unbind" && !mcpWebsiteProxyIncluded(site) { return mcpWebsiteFailure("proxy_not_loaded", true, true) } @@ -1039,23 +1120,6 @@ func (s *MCPWebsiteBindingService) Handle(ctx context.Context, req request.MCPWe if req.Domain != site.PrimaryDomain { return mcpWebsiteFailure("domain_changed", true, true) } - if req.EnableSSL { - installed, installErr := s.openresty() - if installErr != nil || installed.ID == 0 || installed.Status != constant.StatusRunning { - return mcpWebsiteFailure("openresty_unavailable", true, false) - } - host, _, _ := mcpWebsiteDomain(site.PrimaryDomain) - usesHTTPPort := false - for _, domain := range site.Domains { - if domain.Domain == host && domain.Port == installed.HttpPort && !domain.SSL { - usesHTTPPort = true - break - } - } - if !usesHTTPPort { - return mcpWebsiteFailure("http_port_requires_default", true, false) - } - } if site.Protocol == constant.ProtocolHTTPS && !req.EnableSSL || site.Protocol != constant.ProtocolHTTPS && req.EnableSSL || req.EnableSSL && site.WebsiteSSLID != req.SSLID { mutationStarted = true if err = s.updateHTTPS(context.WithoutCancel(ctx), request.WebsiteHTTPSOp{WebsiteID: site.ID, Enable: req.EnableSSL, Type: constant.SSLExisted, WebsiteSSLID: req.SSLID, HttpConfig: constant.HTTPSOnly}); err != nil { diff --git a/core/app/service/mcp_website.go b/core/app/service/mcp_website.go index 95b50a7b1..bf177a171 100644 --- a/core/app/service/mcp_website.go +++ b/core/app/service/mcp_website.go @@ -410,6 +410,7 @@ func (s *MCPRuntimeService) mutateWebsiteLocked(ctx context.Context, pending dto (status.WebsiteID == 0 && status.State != "unbound") { return dto.MCPWebsiteStatus{}, mcpWebsiteOperationError("MCP_CONFLICT", "website_state_unconfirmed", false) } + row.WebsiteID = status.WebsiteID } if pending.Operation == "bind" { if row.WebsiteID != 0 { diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 8a64209cc..60d74a014 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -23,8 +23,9 @@ const isCsrfForbidden = (response?: AxiosResponse) => { return typeof message === 'string' && message.toLowerCase().includes('csrf token invalid'); }; -type RequestConfig = AxiosRequestConfig & { +export type RequestConfig = AxiosRequestConfig & { skipErrorMessage?: boolean; + errorMessageMode?: 'default' | 'manual'; }; class RequestHttp { @@ -79,7 +80,8 @@ class RequestHttp { async (response: AxiosResponse) => { const globalStore = GlobalStore(); const { data } = response; - const authResult = handleAuthResponseCode(data, { showRBACMessage: true }); + const manualErrorMessage = (response.config as RequestConfig)?.errorMessageMode === 'manual'; + const authResult = handleAuthResponseCode(data, { showRBACMessage: !manualErrorMessage }); if (authResult.handled) { if (authResult.action === 'return') { return; @@ -117,12 +119,14 @@ class RequestHttp { return data; } if (data.code && data.code !== ResultEnum.SUCCESS) { - if (data.message.toLowerCase().indexOf('operation not permitted') !== -1) { - MsgError(i18n.global.t('license.tamperHelper')); - return Promise.reject(data); - } - if (!(response.config as RequestConfig).skipErrorMessage) { - MsgError(data.message); + if (!manualErrorMessage) { + if (data.message.toLowerCase().indexOf('operation not permitted') !== -1) { + MsgError(i18n.global.t('license.tamperHelper')); + return Promise.reject(data); + } + if (!(response.config as RequestConfig).skipErrorMessage) { + MsgError(data.message); + } } return Promise.reject(data); } @@ -130,8 +134,11 @@ class RequestHttp { }, async (error: AxiosError) => { const { response } = error; + const manualErrorMessage = + ((error.config ?? response?.config) as RequestConfig | undefined)?.errorMessageMode === 'manual'; - if (error.message.indexOf('timeout') !== -1) MsgError(i18n.global.t('commons.msg.requestTimeout')); + if (!manualErrorMessage && error.message.indexOf('timeout') !== -1) + MsgError(i18n.global.t('commons.msg.requestTimeout')); if (response) { switch (response.status) { case 313: @@ -141,20 +148,24 @@ class RequestHttp { if (isCsrfForbidden(response)) { return Promise.reject(error); } - if (response.data && response.data['message']) { - MsgError(response.data['message']); - } else { - MsgError(i18n.global.t('commons.res.forbidden')); + if (!manualErrorMessage) { + if (response.data && response.data['message']) { + MsgError(response.data['message']); + } else { + MsgError(i18n.global.t('commons.res.forbidden')); + } } return Promise.reject(error); case 500: case 502: case 524: case 407: - checkStatus( - response.status, - response.data && response.data['message'] ? response.data['message'] : '', - ); + if (!manualErrorMessage) { + checkStatus( + response.status, + response.data && response.data['message'] ? response.data['message'] : '', + ); + } return Promise.reject(error); default: return Promise.reject(error); @@ -189,12 +200,14 @@ class RequestHttp { ...config, }); } - postLocalNode(url: string, params?: object, timeout?: number): Promise> { + postLocalNode(url: string, params?: object, timeout?: number, config?: RequestConfig): Promise> { return this.service.post(url, params, { baseURL: import.meta.env.VITE_API_URL as string, timeout: timeout ? timeout : (ResultEnum.TIMEOUT as number), withCredentials: true, + ...config, headers: { + ...config?.headers, CurrentNode: 'local', }, }); diff --git a/frontend/src/api/modules/panel-mcp.ts b/frontend/src/api/modules/panel-mcp.ts index 492e44e49..9252d6d53 100644 --- a/frontend/src/api/modules/panel-mcp.ts +++ b/frontend/src/api/modules/panel-mcp.ts @@ -5,18 +5,19 @@ import type { Container } from '@/api/interface/container'; const prefix = '/core/mcp'; const local = { headers: { CurrentNode: 'local' } }; +const website = { ...local, errorMessageMode: 'manual' as const }; export const getPanelMCPStatus = () => http.get(`${prefix}/status`, {}, local); export const getPanelMCPDockerStatus = () => http.get('/containers/docker/status', {}, { ...local, timeout: 5000 }); export const getPanelMCPSettings = () => http.get(`${prefix}/settings`, {}, local); -export const getPanelMCPWebsite = () => http.get(`${prefix}/website/status`, {}, local); +export const getPanelMCPWebsite = () => http.get(`${prefix}/website/status`, {}, website); export const bindPanelMCPWebsite = (params: PanelMCP.WebsiteBind) => - http.postWithConfig(`${prefix}/website/bind`, params, { ...local, timeout: 120000 }); + http.postWithConfig(`${prefix}/website/bind`, params, { ...website, timeout: 120000 }); export const updatePanelMCPWebsite = (params: PanelMCP.WebsiteUpdate) => - http.postWithConfig(`${prefix}/website/update`, params, { ...local, timeout: 120000 }); + http.postWithConfig(`${prefix}/website/update`, params, { ...website, timeout: 120000 }); export const unbindPanelMCPWebsite = (params: PanelMCP.WebsiteUnbind) => - http.postWithConfig(`${prefix}/website/unbind`, params, { ...local, timeout: 120000 }); + http.postWithConfig(`${prefix}/website/unbind`, params, { ...website, timeout: 120000 }); export const savePanelMCPSettings = (params: PanelMCP.Settings) => http.postWithConfig(`${prefix}/settings`, params, { ...local, timeout: 60000 }); export const operatePanelMCPService = (operation: PanelMCP.Operation) => diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index 3ad59d36f..a0119440e 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -1,4 +1,4 @@ -import http from '@/api'; +import http, { type RequestConfig } from '@/api'; import { ReqPage, ResPage } from '../interface'; import { Website } from '../interface/website'; import { File } from '../interface/file'; @@ -124,8 +124,8 @@ export const listSSL = (req: Website.SSLReq) => { return http.post(`/websites/ssl/list`, req); }; -export const listLocalNodeSSL = (req: Website.SSLReq) => { - return http.postLocalNode(`/websites/ssl/list`, req); +export const listLocalNodeSSL = (req: Website.SSLReq, config?: RequestConfig) => { + return http.postLocalNode(`/websites/ssl/list`, req, undefined, config); }; export const createSSL = (req: Website.SSLCreate) => { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 906369b2d..145c14527 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1826,6 +1826,7 @@ const message = { websiteUnavailable: 'Binding unavailable', websiteDomainInvalid: 'Enter a valid domain, optionally including a port.', websiteCertificateRequired: 'Select a valid certificate covering this domain.', + websiteCertificatesLoadFailed: 'Cannot load certificates. Refresh to retry.', websiteCertificateEmpty: 'No matching certificate. Apply for or import one.', websiteUnbind: 'Unbind and delete website', websiteUnbindConfirm: diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 41b8aecf6..d1c6921e5 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -1842,6 +1842,7 @@ const message = { websiteUnavailable: 'Vinculación no disponible', websiteDomainInvalid: 'Introduzca un dominio válido, con un puerto opcional.', websiteCertificateRequired: 'Seleccione un certificado válido que cubra este dominio.', + websiteCertificatesLoadFailed: 'No se pueden cargar los certificados. Actualice para reintentar.', websiteCertificateEmpty: 'No hay un certificado coincidente. Solicite o importe uno.', websiteUnbind: 'Desvincular y eliminar sitio web', websiteUnbindConfirm: diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts index b6dc3a34d..4a6405a0f 100644 --- a/frontend/src/lang/modules/fa.ts +++ b/frontend/src/lang/modules/fa.ts @@ -1808,6 +1808,7 @@ const message = { websiteUnavailable: 'اتصال در دسترس نیست', websiteDomainInvalid: 'یک دامنه معتبر وارد کنید؛ می‌توانید پورت را نیز مشخص کنید.', websiteCertificateRequired: 'یک گواهی معتبر برای این دامنه انتخاب کنید.', + websiteCertificatesLoadFailed: 'بارگیری فهرست گواهی‌ها ناموفق بود. تازه‌سازی کرده و دوباره تلاش کنید.', websiteCertificateEmpty: 'گواهی منطبق وجود ندارد. گواهی درخواست یا وارد کنید.', websiteUnbind: 'قطع اتصال و حذف وب‌سایت', websiteUnbindConfirm: 'اتصال قطع و سایت و پوشه آن حذف شود؟ گواهی حفظ می‌شود. اتصال مجدد سایت تازه‌ای می‌سازد.', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 10a514dd7..8932a5a6f 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -1809,6 +1809,7 @@ const message = { websiteUnavailable: '関連付けを利用できません', websiteDomainInvalid: '有効なドメインを入力してください。ポートも指定できます。', websiteCertificateRequired: 'このドメインを対象とする有効な証明書を選択してください。', + websiteCertificatesLoadFailed: '証明書一覧を読み込めません。更新して再試行してください。', websiteCertificateEmpty: '一致する証明書がありません。申請またはインポートしてください。', websiteUnbind: '関連付けを解除してサイトを削除', websiteUnbindConfirm: diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 128cf7cd8..dbb07e44b 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -1794,6 +1794,7 @@ const message = { websiteUnavailable: '연결 사용 불가', websiteDomainInvalid: '유효한 도메인을 입력하세요. 포트를 포함할 수 있습니다.', websiteCertificateRequired: '이 도메인에 유효한 인증서를 선택하세요.', + websiteCertificatesLoadFailed: '인증서 목록을 불러올 수 없습니다. 새로 고침 후 다시 시도하세요.', websiteCertificateEmpty: '일치하는 인증서가 없습니다. 신청하거나 가져오세요.', websiteUnbind: '연결 해제 및 웹사이트 삭제', websiteUnbindConfirm: diff --git a/frontend/src/lang/modules/lo.ts b/frontend/src/lang/modules/lo.ts index 5e5d3881e..8ffec4a03 100644 --- a/frontend/src/lang/modules/lo.ts +++ b/frontend/src/lang/modules/lo.ts @@ -1784,6 +1784,7 @@ const message = { websiteUnavailable: 'ການຜູກບໍ່ພ້ອມໃຊ້', websiteDomainInvalid: 'ປ້ອນໂດເມນທີ່ຖືກຕ້ອງ ໂດຍສາມາດລະບຸພອດໄດ້.', websiteCertificateRequired: 'ເລືອກໃບຢັ້ງຢືນທີ່ຍັງໃຊ້ໄດ້ ແລະຄອບຄຸມໂດເມນນີ້.', + websiteCertificatesLoadFailed: 'ບໍ່ສາມາດໂຫຼດລາຍການໃບຮັບຮອງໄດ້. ກະລຸນາໂຫຼດໜ້າໃໝ່ແລ້ວລອງອີກຄັ້ງ.', websiteCertificateEmpty: 'ບໍ່ມີໃບຮັບຮອງທີ່ກົງກັນ. ຂໍ ຫຼືນຳເຂົ້າໃບຮັບຮອງ.', websiteUnbind: 'ຍົກເລີກການຜູກ ແລະ ລຶບເວັບໄຊ', websiteUnbindConfirm: 'ຍົກເລີກການຜູກ ແລະລຶບເວັບໄຊພ້ອມໂຟນເດີບໍ? ເກັບໃບຮັບຮອງໄວ້. ຜູກໃໝ່ຈະສ້າງເວັບໄຊໃໝ່.', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 350411169..49bf7ec39 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1841,6 +1841,7 @@ const message = { websiteUnavailable: 'Pautan tidak tersedia', websiteDomainInvalid: 'Masukkan domain yang sah, dengan port pilihan.', websiteCertificateRequired: 'Pilih sijil sah yang meliputi domain ini.', + websiteCertificatesLoadFailed: 'Tidak dapat memuatkan senarai sijil. Muat semula untuk mencuba lagi.', websiteCertificateEmpty: 'Tiada sijil sepadan. Mohon atau import sijil.', websiteUnbind: 'Nyahpaut dan padam laman web', websiteUnbindConfirm: diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 9a17e53b7..5c0aae457 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1841,6 +1841,7 @@ const message = { websiteUnavailable: 'Vinculação indisponível', websiteDomainInvalid: 'Informe um domínio válido, com uma porta opcional.', websiteCertificateRequired: 'Selecione um certificado válido que cubra este domínio.', + websiteCertificatesLoadFailed: 'Não foi possível carregar os certificados. Atualize para tentar novamente.', websiteCertificateEmpty: 'Nenhum certificado correspondente. Solicite ou importe um.', websiteUnbind: 'Desvincular e excluir site', websiteUnbindConfirm: diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index c6c358aa9..513b6863d 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1832,6 +1832,7 @@ const message = { websiteUnavailable: 'Привязка недоступна', websiteDomainInvalid: 'Введите допустимый домен, при необходимости с портом.', websiteCertificateRequired: 'Выберите действующий сертификат для этого домена.', + websiteCertificatesLoadFailed: 'Не удалось загрузить список сертификатов. Обновите и повторите попытку.', websiteCertificateEmpty: 'Нет подходящего сертификата. Выпустите или импортируйте его.', websiteUnbind: 'Отвязать и удалить сайт', websiteUnbindConfirm: diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 335b43232..339bbc331 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -1836,6 +1836,7 @@ const message = { websiteUnavailable: 'Bağlantı kullanılamıyor', websiteDomainInvalid: 'Geçerli bir alan adı girin; isteğe bağlı olarak bağlantı noktası ekleyebilirsiniz.', websiteCertificateRequired: 'Bu alan adını kapsayan geçerli bir sertifika seçin.', + websiteCertificatesLoadFailed: 'Sertifika listesi yüklenemedi. Yenileyip tekrar deneyin.', websiteCertificateEmpty: 'Uygun sertifika yok. Başvurun veya içe aktarın.', websiteUnbind: 'Bağlantıyı kaldır ve siteyi sil', websiteUnbindConfirm: diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 702e5dd21..9c815bb1a 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -1731,6 +1731,7 @@ const message = { websiteUnavailable: '綁定不可用', websiteDomainInvalid: '請輸入有效網域,可包含連接埠。', websiteCertificateRequired: '請選擇涵蓋此網域且有效的憑證。', + websiteCertificatesLoadFailed: '憑證清單讀取失敗,請重新整理後重試。', websiteCertificateEmpty: '無符合憑證,請先申請或匯入。', websiteUnbind: '解除綁定並刪除網站', websiteUnbindConfirm: '解除綁定並刪除網站及目錄?憑證保留,重新綁定將建立新網站。', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 0fec8a409..bab3d79a8 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1745,6 +1745,7 @@ const message = { websiteUnavailable: '绑定不可用', websiteDomainInvalid: '请输入有效域名,可包含端口。', websiteCertificateRequired: '请选择覆盖该域名且有效的证书。', + websiteCertificatesLoadFailed: '证书列表读取失败,请刷新重试。', websiteCertificateEmpty: '暂无匹配证书,请先申请或导入。', websiteUnbind: '解绑并删除网站', websiteUnbindConfirm: '解绑并删除网站及目录?证书保留,重新绑定将新建网站。', diff --git a/frontend/src/views/ai/mcp/panel/website-binding.vue b/frontend/src/views/ai/mcp/panel/website-binding.vue index 7001f9b7a..35717970c 100644 --- a/frontend/src/views/ai/mcp/panel/website-binding.vue +++ b/frontend/src/views/ai/mcp/panel/website-binding.vue @@ -4,23 +4,22 @@ {{ $t('panelMCP.websiteBinding') }} - {{ $t(`panelMCP.${stateLabels[binding.state]}`) }} + {{ $t(`panelMCP.${stateLabels[pending ? 'pending' : binding.state]}`) }} - - @@ -130,7 +127,7 @@ {{ $t('panelMCP.websiteCertificateUpdate') }} - + {{ $t('panelMCP.websiteUnbind') }} @@ -157,6 +154,7 @@