diff --git a/agent/app/api/v2/php_extensions.go b/agent/app/api/v2/php_extensions.go index 842ea43a5..ea4f0c77a 100644 --- a/agent/app/api/v2/php_extensions.go +++ b/agent/app/api/v2/php_extensions.go @@ -103,3 +103,46 @@ func (b *BaseApi) DeletePHPExtensions(c *gin.Context) { } helper.Success(c) } + +// @Router /runtimes/php/extensions/detail [post] +func (b *BaseApi) GetPHPExtensions(c *gin.Context) { + var req request.PHPExtensionsDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + item, err := phpExtensionsService.Get(req.ID) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, item) +} + +// @Router /runtimes/php/extensions/import [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 PHP 扩展模版 [name]","formatEN":"Sync PHP extension template [name]"} +func (b *BaseApi) ImportPHPExtensions(c *gin.Context) { + var req request.PHPExtensionsCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + status, err := phpExtensionsService.Import(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, status) +} + +// @Router /runtimes/php/extensions/push [post] +// @x-panel-log {"bodyKeys":["sourceNode","targetNodes"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"推送 PHP 扩展模版 [sourceNode] 至 [targetNodes]","formatEN":"Push PHP extension template from [sourceNode] to [targetNodes]"} +func (b *BaseApi) PushPHPExtensions(c *gin.Context) { + var req dto.PHPExtensionsPush + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := phpExtensionsService.Push(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/dto/php_extensions.go b/agent/app/dto/php_extensions.go new file mode 100644 index 000000000..6588d1318 --- /dev/null +++ b/agent/app/dto/php_extensions.go @@ -0,0 +1,9 @@ +package dto + +// PHPExtensionsPush is submitted to the master agent, with an explicit source node. +type PHPExtensionsPush struct { + SourceNode string `json:"sourceNode" validate:"required"` + TargetNodes []string `json:"targetNodes" validate:"required,min=1,dive,required"` + ID uint `json:"id" validate:"required"` + TaskID string `json:"taskID" validate:"required,uuid"` +} diff --git a/agent/app/dto/request/php_extensions.go b/agent/app/dto/request/php_extensions.go index 2046f2b08..f751369ac 100644 --- a/agent/app/dto/request/php_extensions.go +++ b/agent/app/dto/request/php_extensions.go @@ -4,7 +4,8 @@ import "github.com/1Panel-dev/1Panel/agent/app/dto" type PHPExtensionsSearch struct { dto.PageInfo - All bool `json:"all"` + All bool `json:"all"` + Name string `json:"name"` } type PHPExtensionsCreate struct { diff --git a/agent/app/service/php_extensions.go b/agent/app/service/php_extensions.go index 6698150aa..d890971fc 100644 --- a/agent/app/service/php_extensions.go +++ b/agent/app/service/php_extensions.go @@ -1,13 +1,22 @@ package service import ( + "errors" + "sync" + + "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto/request" "github.com/1Panel-dev/1Panel/agent/app/dto/response" "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/repo" "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + "gorm.io/gorm" ) +var phpExtensionsMu sync.Mutex + type PHPExtensionsService struct { } @@ -17,6 +26,9 @@ type IPHPExtensionsService interface { Create(req request.PHPExtensionsCreate) error Update(req request.PHPExtensionsUpdate) error Delete(req request.PHPExtensionsDelete) error + Get(id uint) (model.PHPExtensions, error) + Import(req request.PHPExtensionsCreate) (string, error) + Push(req dto.PHPExtensionsPush) error } func NewIPHPExtensionsService() IPHPExtensionsService { @@ -30,7 +42,7 @@ func (p PHPExtensionsService) Page(req request.PHPExtensionsSearch) (int64, []re err error result []response.PHPExtensionsDTO ) - total, extensions, err = phpExtensionsRepo.Page(req.Page, req.PageSize) + total, extensions, err = phpExtensionsRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Name)) if err != nil { return 0, nil, err } @@ -61,6 +73,8 @@ func (p PHPExtensionsService) List() ([]response.PHPExtensionsDTO, error) { } func (p PHPExtensionsService) Create(req request.PHPExtensionsCreate) error { + phpExtensionsMu.Lock() + defer phpExtensionsMu.Unlock() exist, _ := phpExtensionsRepo.GetFirst(repo.WithByName(req.Name)) if exist.ID > 0 { return buserr.New("ErrNameIsExist") @@ -73,6 +87,8 @@ func (p PHPExtensionsService) Create(req request.PHPExtensionsCreate) error { } func (p PHPExtensionsService) Update(req request.PHPExtensionsUpdate) error { + phpExtensionsMu.Lock() + defer phpExtensionsMu.Unlock() exist, err := phpExtensionsRepo.GetFirst(repo.WithByID(req.ID)) if err != nil { return err @@ -82,5 +98,41 @@ func (p PHPExtensionsService) Update(req request.PHPExtensionsUpdate) error { } func (p PHPExtensionsService) Delete(req request.PHPExtensionsDelete) error { + phpExtensionsMu.Lock() + defer phpExtensionsMu.Unlock() return phpExtensionsRepo.DeleteBy(repo.WithByID(req.ID)) } + +func (p PHPExtensionsService) Get(id uint) (model.PHPExtensions, error) { + return phpExtensionsRepo.GetFirst(repo.WithByID(id)) +} + +func (p PHPExtensionsService) Import(req request.PHPExtensionsCreate) (string, error) { + phpExtensionsMu.Lock() + defer phpExtensionsMu.Unlock() + existing, err := phpExtensionsRepo.GetFirst(repo.WithByName(req.Name)) + if err == nil { + if existing.Extensions == req.Extensions { + return "skipped", nil + } + return "", buserr.New("ErrPHPExtensionsConflict") + } + if !errors.Is(err, gorm.ErrRecordNotFound) { + return "", err + } + item := model.PHPExtensions{Name: req.Name, Extensions: req.Extensions} + if err := phpExtensionsRepo.Create(&item); err != nil { + return "", err + } + return "created", nil +} + +func (p PHPExtensionsService) Push(req dto.PHPExtensionsPush) error { + if !global.IsMaster { + return errors.New("PHP extension template sync must be submitted to the master node") + } + if !xpack.MultiNodeProvider.IsXpack() { + return buserr.New("ErrXpackNotFound") + } + return xpack.MultiNodeProvider.PushPHPExtensionsToNode(req) +} diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 9c4c542be..abd1f06b4 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -815,3 +815,5 @@ ContainerImageCheckSummary: "Check finished: {{ .checked }} checked, {{ .updates ContainerImageCheckFailures: "{{ .count }} container checks failed; see the task log" ErrImageCheckProxyRequired: "To check for image updates in the current runtime environment, please configure a proxy server first." + +ErrPHPExtensionsConflict: "A PHP extension template with this name exists with different content" diff --git a/agent/i18n/lang/es-ES.yaml b/agent/i18n/lang/es-ES.yaml index 4bcacb160..455477b04 100644 --- a/agent/i18n/lang/es-ES.yaml +++ b/agent/i18n/lang/es-ES.yaml @@ -815,3 +815,5 @@ ContainerImageCheckSummary: "Comprobación finalizada: comprobados {{ .checked } ContainerImageCheckFailures: "Falló la comprobación de {{ .count }} contenedores; consulte el registro de la tarea" ErrImageCheckProxyRequired: "Para comprobar actualizaciones de imágenes en el entorno actual, configura primero un servidor proxy." + +ErrPHPExtensionsConflict: "A PHP extension template with this name exists with different content" diff --git a/agent/i18n/lang/fa.yaml b/agent/i18n/lang/fa.yaml index 3baf644f8..434fa7f36 100644 --- a/agent/i18n/lang/fa.yaml +++ b/agent/i18n/lang/fa.yaml @@ -816,3 +816,5 @@ ContainerImageCheckSummary: "بررسی پایان یافت: بررسی‌شده ContainerImageCheckFailures: "بررسی {{ .count }} کانتینر ناموفق بود؛ گزارش وظیفه را مشاهده کنید" ErrImageCheckProxyRequired: "برای بررسی به‌روزرسانی تصاویر در محیط فعلی، ابتدا یک سرور پروکسی پیکربندی کنید." + +ErrPHPExtensionsConflict: "A PHP extension template with this name exists with different content" diff --git a/agent/i18n/lang/ja.yaml b/agent/i18n/lang/ja.yaml index 128051878..89260cff7 100644 --- a/agent/i18n/lang/ja.yaml +++ b/agent/i18n/lang/ja.yaml @@ -815,3 +815,5 @@ ContainerImageCheckSummary: "確認終了:成功 {{ .checked }}、更新可能 ContainerImageCheckFailures: "{{ .count }} コンテナの確認に失敗しました。タスクログを確認してください" ErrImageCheckProxyRequired: "現在の実行環境でイメージの更新を確認するには、先にプロキシサーバーを設定してください。" + +ErrPHPExtensionsConflict: "A PHP extension template with this name exists with different content" diff --git a/agent/i18n/lang/ko.yaml b/agent/i18n/lang/ko.yaml index 91e1105e4..7bcd6ba60 100644 --- a/agent/i18n/lang/ko.yaml +++ b/agent/i18n/lang/ko.yaml @@ -815,3 +815,5 @@ ContainerImageCheckSummary: "확인 종료: 성공 {{ .checked }}, 업데이트 ContainerImageCheckFailures: "컨테이너 {{ .count }}개 확인에 실패했습니다. 작업 로그를 확인하세요" ErrImageCheckProxyRequired: "현재 실행 환경에서 이미지 업데이트를 확인하려면 먼저 프록시 서버를 설정하세요." + +ErrPHPExtensionsConflict: "A PHP extension template with this name exists with different content" diff --git a/agent/i18n/lang/lo.yaml b/agent/i18n/lang/lo.yaml index d930c002f..c55455fb5 100644 --- a/agent/i18n/lang/lo.yaml +++ b/agent/i18n/lang/lo.yaml @@ -806,3 +806,5 @@ ContainerImageCheckSummary: "ກວດສອບສິ້ນສຸດ: ກວດ ContainerImageCheckFailures: "ກວດສອບ {{ .count }} ຄອນເທນເນີລົ້ມເຫຼວ; ກະລຸນາເບິ່ງບັນທຶກວຽກ" ErrImageCheckProxyRequired: "ເພື່ອກວດສອບການອັບເດດອິມເມຈໃນສະພາບແວດລ້ອມປັດຈຸບັນ ກະລຸນາຕັ້ງຄ່າພຣັອກຊີເຊີບເວີກ່ອນ." + +ErrPHPExtensionsConflict: "A PHP extension template with this name exists with different content" diff --git a/agent/i18n/lang/ms.yaml b/agent/i18n/lang/ms.yaml index 097e8ad8c..36bb960c1 100644 --- a/agent/i18n/lang/ms.yaml +++ b/agent/i18n/lang/ms.yaml @@ -815,3 +815,5 @@ ContainerImageCheckSummary: "Semakan selesai: disemak {{ .checked }}, boleh dike ContainerImageCheckFailures: "Semakan {{ .count }} kontena gagal; lihat log tugas" ErrImageCheckProxyRequired: "Untuk menyemak kemas kini imej dalam persekitaran semasa, sila konfigurasikan pelayan proksi terlebih dahulu." + +ErrPHPExtensionsConflict: "A PHP extension template with this name exists with different content" diff --git a/agent/i18n/lang/pt-BR.yaml b/agent/i18n/lang/pt-BR.yaml index f7c040f54..9d23a29e4 100644 --- a/agent/i18n/lang/pt-BR.yaml +++ b/agent/i18n/lang/pt-BR.yaml @@ -815,3 +815,5 @@ ContainerImageCheckSummary: "Verificação concluída: verificados {{ .checked } ContainerImageCheckFailures: "Falha na verificação de {{ .count }} contêineres; consulte o log da tarefa" ErrImageCheckProxyRequired: "Para verificar atualizações de imagens no ambiente atual, configure primeiro um servidor proxy." + +ErrPHPExtensionsConflict: "A PHP extension template with this name exists with different content" diff --git a/agent/i18n/lang/ru.yaml b/agent/i18n/lang/ru.yaml index 4494dab1b..291cd4639 100644 --- a/agent/i18n/lang/ru.yaml +++ b/agent/i18n/lang/ru.yaml @@ -815,3 +815,5 @@ ContainerImageCheckSummary: "Проверка завершена: провере ContainerImageCheckFailures: "Не удалось проверить контейнеры: {{ .count }}. Подробности в журнале задачи" ErrImageCheckProxyRequired: "Для проверки обновлений образов в текущей среде сначала настройте прокси-сервер." + +ErrPHPExtensionsConflict: "A PHP extension template with this name exists with different content" diff --git a/agent/i18n/lang/tr.yaml b/agent/i18n/lang/tr.yaml index 045e17f85..5d6a0c34f 100644 --- a/agent/i18n/lang/tr.yaml +++ b/agent/i18n/lang/tr.yaml @@ -815,3 +815,5 @@ ContainerImageCheckSummary: "Denetim tamamlandı: denetlenen {{ .checked }}, gü ContainerImageCheckFailures: "{{ .count }} konteyner denetlenemedi; görev günlüğünü inceleyin" ErrImageCheckProxyRequired: "Geçerli çalışma ortamında imaj güncellemelerini kontrol etmek için önce bir proxy sunucusu yapılandırın." + +ErrPHPExtensionsConflict: "A PHP extension template with this name exists with different content" diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index 1401621de..201a78c1b 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -815,3 +815,5 @@ ContainerImageCheckSummary: "檢查結束:成功 {{ .checked }},可升級 {{ ContainerImageCheckFailures: "{{ .count }} 個容器檢查失敗,請查看任務日誌" ErrImageCheckProxyRequired: "目前運行環境下,如需檢查映像是否更新,請先設定代理伺服器" + +ErrPHPExtensionsConflict: "同名 PHP 擴充模版已存在且內容不同,未覆蓋" diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index 0dc1cc9a0..fe6672938 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -815,3 +815,5 @@ ContainerImageCheckSummary: "检查结束:成功 {{ .checked }},可升级 {{ ContainerImageCheckFailures: "{{ .count }} 个容器检查失败,请查看任务日志" ErrImageCheckProxyRequired: "当前运行环境下,如需检查镜像是否更新,请优先配置代理服务器" + +ErrPHPExtensionsConflict: "同名 PHP 扩展模版已存在且内容不同,未覆盖" diff --git a/agent/router/ro_runtime.go b/agent/router/ro_runtime.go index 7f03a8ad8..9f1568f3d 100644 --- a/agent/router/ro_runtime.go +++ b/agent/router/ro_runtime.go @@ -27,6 +27,9 @@ func (r *RuntimeRouter) InitRouter(Router *gin.RouterGroup) { groupRouter.POST("/node/modules/operate", baseApi.OperateNodeModules) groupRouter.POST("/php/extensions/search", baseApi.PagePHPExtensions) + groupRouter.POST("/php/extensions/detail", baseApi.GetPHPExtensions) + groupRouter.POST("/php/extensions/import", baseApi.ImportPHPExtensions) + groupRouter.POST("/php/extensions/push", baseApi.PushPHPExtensions) groupRouter.POST("/php/extensions", baseApi.CreatePHPExtensions) groupRouter.POST("/php/extensions/update", baseApi.UpdatePHPExtensions) groupRouter.POST("/php/extensions/del", baseApi.DeletePHPExtensions) diff --git a/agent/utils/xpack/helper/multi_node.go b/agent/utils/xpack/helper/multi_node.go index 1c4700711..849130751 100644 --- a/agent/utils/xpack/helper/multi_node.go +++ b/agent/utils/xpack/helper/multi_node.go @@ -72,3 +72,7 @@ func (m *multiNodeHelper) PushSSLToNode(websiteSSL *model.WebsiteSSL) error { func (m *multiNodeHelper) GetAgentInfo() (*dto.AgentInfo, error) { return nil, nil } + +func (m *multiNodeHelper) PushPHPExtensionsToNode(req dto.PHPExtensionsPush) error { + return buserr.New("ErrXpackNotFound") +} diff --git a/agent/utils/xpack/providers/multi_node.go b/agent/utils/xpack/providers/multi_node.go index 5defd2cc7..44f11cd6e 100644 --- a/agent/utils/xpack/providers/multi_node.go +++ b/agent/utils/xpack/providers/multi_node.go @@ -19,5 +19,6 @@ type MultiNodeProvider interface { LoadRequestTransport() *http.Transport ValidateCertificate(c *gin.Context) bool PushSSLToNode(websiteSSL *model.WebsiteSSL) error + PushPHPExtensionsToNode(req dto.PHPExtensionsPush) error GetAgentInfo() (*dto.AgentInfo, error) } diff --git a/core/constant/common.go b/core/constant/common.go index 4b7055d85..0e49ca5f2 100644 --- a/core/constant/common.go +++ b/core/constant/common.go @@ -189,21 +189,22 @@ var WebUrlMap = map[string]struct{}{ "/xpack/monitor/trend": {}, "/xpack/monitor/websites": {}, - "/xpack/tamper": {}, - "/xpack/gpu": {}, - "/xpack/alert/dashboard": {}, - "/xpack/alert/log": {}, - "/xpack/alert/setting": {}, - "/xpack/setting": {}, - "/xpack/node/dashboard": {}, - "/xpack/node": {}, - "/xpack/simple-node": {}, - "/xpack/sync/file": {}, - "/xpack/sync/image": {}, - "/xpack/sync/ssl": {}, - "/xpack/sync/app": {}, - "/xpack/app": {}, - "/xpack/app-upgrade": {}, + "/xpack/tamper": {}, + "/xpack/gpu": {}, + "/xpack/alert/dashboard": {}, + "/xpack/alert/log": {}, + "/xpack/alert/setting": {}, + "/xpack/setting": {}, + "/xpack/node/dashboard": {}, + "/xpack/node": {}, + "/xpack/simple-node": {}, + "/xpack/sync/file": {}, + "/xpack/sync/image": {}, + "/xpack/sync/php-extensions": {}, + "/xpack/sync/ssl": {}, + "/xpack/sync/app": {}, + "/xpack/app": {}, + "/xpack/app-upgrade": {}, "/xpack/cluster/mysql": {}, "/xpack/cluster/postgres": {}, diff --git a/core/i18n/lang/en.yaml b/core/i18n/lang/en.yaml index 8e9268614..9a518edc5 100644 --- a/core/i18n/lang/en.yaml +++ b/core/i18n/lang/en.yaml @@ -587,6 +587,10 @@ MCPInstallRecoveryFailed: "Installation failed and service recovery is incomplet MCPInstallLocalImage: "The image exists locally; skip downloading" MCPInstallVerifyImage: "Validate the image version and managed service contract" MCPInstallVerifiedImage: "Image validation passed" + +PHPExtensionsNoTargets: "No target nodes remain after excluding the source node" +PHPExtensionsSyncTask: "Sync PHP extension template [{{ .name }}]" +PHPExtensionsSyncSkipped: "Node [{{ .node }}] already has an identical template; skipped" MCPInstallCleanup: "Clean up the previous container" MCPInstallOldContainerRemoved: "Previous container removed" MCPInstallOldImageRemoved: "Previous image removed" diff --git a/core/i18n/lang/es-ES.yaml b/core/i18n/lang/es-ES.yaml index 86348c140..6ea202d85 100644 --- a/core/i18n/lang/es-ES.yaml +++ b/core/i18n/lang/es-ES.yaml @@ -587,6 +587,10 @@ MCPInstallRecoveryFailed: "Instalación fallida y recuperación incompleta. Veri MCPInstallLocalImage: "La imagen ya existe localmente; omitir la descarga" MCPInstallVerifyImage: "Validar la versión de la imagen y el contrato del servicio administrado" MCPInstallVerifiedImage: "La imagen superó la validación" + +PHPExtensionsNoTargets: "No target nodes remain after excluding the source node" +PHPExtensionsSyncTask: "Sync PHP extension template [{{ .name }}]" +PHPExtensionsSyncSkipped: "Node [{{ .node }}] already has an identical template; skipped" MCPInstallCleanup: "Limpiar el contenedor anterior" MCPInstallOldContainerRemoved: "Contenedor anterior eliminado" MCPInstallOldImageRemoved: "Imagen anterior eliminada" diff --git a/core/i18n/lang/fa.yaml b/core/i18n/lang/fa.yaml index 5d0d1f4df..09ce560cf 100644 --- a/core/i18n/lang/fa.yaml +++ b/core/i18n/lang/fa.yaml @@ -587,6 +587,10 @@ MCPInstallRecoveryFailed: "نصب ناموفق و بازیابی سرویس نا MCPInstallLocalImage: "تصویر به‌صورت محلی موجود است؛ دریافت انجام نمی‌شود" MCPInstallVerifyImage: "اعتبارسنجی نسخه تصویر و قرارداد سرویس مدیریت‌شده" MCPInstallVerifiedImage: "اعتبارسنجی تصویر موفق بود" + +PHPExtensionsNoTargets: "No target nodes remain after excluding the source node" +PHPExtensionsSyncTask: "Sync PHP extension template [{{ .name }}]" +PHPExtensionsSyncSkipped: "Node [{{ .node }}] already has an identical template; skipped" MCPInstallCleanup: "پاک‌سازی کانتینر قبلی" MCPInstallOldContainerRemoved: "کانتینر قبلی حذف شد" MCPInstallOldImageRemoved: "ایمیج قبلی حذف شد" diff --git a/core/i18n/lang/ja.yaml b/core/i18n/lang/ja.yaml index 39c0de83f..0a07f5d9c 100644 --- a/core/i18n/lang/ja.yaml +++ b/core/i18n/lang/ja.yaml @@ -587,6 +587,10 @@ MCPInstallRecoveryFailed: "インストールに失敗し、サービスの復 MCPInstallLocalImage: "ローカルにイメージが存在するため、ダウンロードをスキップ" MCPInstallVerifyImage: "イメージのバージョンと管理対象サービスの契約を検証" MCPInstallVerifiedImage: "イメージの検証に成功しました" + +PHPExtensionsNoTargets: "No target nodes remain after excluding the source node" +PHPExtensionsSyncTask: "Sync PHP extension template [{{ .name }}]" +PHPExtensionsSyncSkipped: "Node [{{ .node }}] already has an identical template; skipped" MCPInstallCleanup: "以前のコンテナーをクリーンアップ" MCPInstallOldContainerRemoved: "以前のコンテナーを削除しました" MCPInstallOldImageRemoved: "以前のイメージを削除しました" diff --git a/core/i18n/lang/ko.yaml b/core/i18n/lang/ko.yaml index 448da47db..4b905dc04 100644 --- a/core/i18n/lang/ko.yaml +++ b/core/i18n/lang/ko.yaml @@ -587,6 +587,10 @@ MCPInstallRecoveryFailed: "설치에 실패했고 서비스 복원이 불완전 MCPInstallLocalImage: "로컬 이미지가 있어 다운로드를 건너뜁니다" MCPInstallVerifyImage: "이미지 버전 및 관리형 서비스 계약 검증" MCPInstallVerifiedImage: "이미지 검증 통과" + +PHPExtensionsNoTargets: "No target nodes remain after excluding the source node" +PHPExtensionsSyncTask: "Sync PHP extension template [{{ .name }}]" +PHPExtensionsSyncSkipped: "Node [{{ .node }}] already has an identical template; skipped" MCPInstallCleanup: "이전 컨테이너 정리" MCPInstallOldContainerRemoved: "이전 컨테이너가 삭제되었습니다" MCPInstallOldImageRemoved: "이전 이미지가 삭제되었습니다" diff --git a/core/i18n/lang/lo.yaml b/core/i18n/lang/lo.yaml index cff4834f8..2506e43fb 100644 --- a/core/i18n/lang/lo.yaml +++ b/core/i18n/lang/lo.yaml @@ -587,6 +587,10 @@ MCPInstallRecoveryFailed: "ຕິດຕັ້ງບໍ່ສຳເລັດແ MCPInstallLocalImage: "ມີອິມເມດຢູ່ໃນເຄື່ອງແລ້ວ; ຂ້າມການດາວໂຫຼດ" MCPInstallVerifyImage: "ກວດສອບເວີຊັນອິມເມດ ແລະ ສັນຍາບໍລິການທີ່ຈັດການ" MCPInstallVerifiedImage: "ກວດສອບອິມເມດຜ່ານແລ້ວ" + +PHPExtensionsNoTargets: "No target nodes remain after excluding the source node" +PHPExtensionsSyncTask: "Sync PHP extension template [{{ .name }}]" +PHPExtensionsSyncSkipped: "Node [{{ .node }}] already has an identical template; skipped" MCPInstallCleanup: "ກຳລັງລ້າງຄອນເທນເນີເກົ່າ" MCPInstallOldContainerRemoved: "ລຶບຄອນເທນເນີເກົ່າແລ້ວ" MCPInstallOldImageRemoved: "ລຶບອິມເມຈເກົ່າແລ້ວ" diff --git a/core/i18n/lang/ms.yaml b/core/i18n/lang/ms.yaml index e678b5484..9a0da75f5 100644 --- a/core/i18n/lang/ms.yaml +++ b/core/i18n/lang/ms.yaml @@ -587,6 +587,10 @@ MCPInstallRecoveryFailed: "Pemasangan gagal dan pemulihan tidak lengkap. Sahkan MCPInstallLocalImage: "Imej sudah tersedia secara setempat; langkau muat turun" MCPInstallVerifyImage: "Sahkan versi imej dan kontrak perkhidmatan terurus" MCPInstallVerifiedImage: "Pengesahan imej berjaya" + +PHPExtensionsNoTargets: "No target nodes remain after excluding the source node" +PHPExtensionsSyncTask: "Sync PHP extension template [{{ .name }}]" +PHPExtensionsSyncSkipped: "Node [{{ .node }}] already has an identical template; skipped" MCPInstallCleanup: "Bersihkan bekas terdahulu" MCPInstallOldContainerRemoved: "Bekas terdahulu dipadam" MCPInstallOldImageRemoved: "Imej terdahulu dipadam" diff --git a/core/i18n/lang/pt-BR.yaml b/core/i18n/lang/pt-BR.yaml index ef2826938..5f28b98f2 100644 --- a/core/i18n/lang/pt-BR.yaml +++ b/core/i18n/lang/pt-BR.yaml @@ -587,6 +587,10 @@ MCPInstallRecoveryFailed: "Instalação falhou e a recuperação está incomplet MCPInstallLocalImage: "A imagem já existe localmente; ignorar o download" MCPInstallVerifyImage: "Validar a versão da imagem e o contrato do serviço gerenciado" MCPInstallVerifiedImage: "A imagem passou na validação" + +PHPExtensionsNoTargets: "No target nodes remain after excluding the source node" +PHPExtensionsSyncTask: "Sync PHP extension template [{{ .name }}]" +PHPExtensionsSyncSkipped: "Node [{{ .node }}] already has an identical template; skipped" MCPInstallCleanup: "Limpar o contêiner anterior" MCPInstallOldContainerRemoved: "Contêiner anterior removido" MCPInstallOldImageRemoved: "Imagem anterior removida" diff --git a/core/i18n/lang/ru.yaml b/core/i18n/lang/ru.yaml index 35c902ee7..0648a85ec 100644 --- a/core/i18n/lang/ru.yaml +++ b/core/i18n/lang/ru.yaml @@ -587,6 +587,10 @@ MCPInstallRecoveryFailed: "Установка не удалась, служба MCPInstallLocalImage: "Образ уже есть локально; пропустить загрузку" MCPInstallVerifyImage: "Проверить версию образа и контракт управляемой службы" MCPInstallVerifiedImage: "Образ успешно проверен" + +PHPExtensionsNoTargets: "No target nodes remain after excluding the source node" +PHPExtensionsSyncTask: "Sync PHP extension template [{{ .name }}]" +PHPExtensionsSyncSkipped: "Node [{{ .node }}] already has an identical template; skipped" MCPInstallCleanup: "Очистка предыдущего контейнера" MCPInstallOldContainerRemoved: "Предыдущий контейнер удалён" MCPInstallOldImageRemoved: "Предыдущий образ удалён" diff --git a/core/i18n/lang/tr.yaml b/core/i18n/lang/tr.yaml index 7dd2d5f03..7693dca91 100644 --- a/core/i18n/lang/tr.yaml +++ b/core/i18n/lang/tr.yaml @@ -587,6 +587,10 @@ MCPInstallRecoveryFailed: "Kurulum başarısız ve hizmet tam geri yüklenemedi. MCPInstallLocalImage: "İmaj yerel olarak mevcut; indirmeyi atla" MCPInstallVerifyImage: "İmaj sürümünü ve yönetilen hizmet sözleşmesini doğrula" MCPInstallVerifiedImage: "İmaj doğrulaması başarılı" + +PHPExtensionsNoTargets: "No target nodes remain after excluding the source node" +PHPExtensionsSyncTask: "Sync PHP extension template [{{ .name }}]" +PHPExtensionsSyncSkipped: "Node [{{ .node }}] already has an identical template; skipped" MCPInstallCleanup: "Önceki konteyneri temizle" MCPInstallOldContainerRemoved: "Önceki konteyner silindi" MCPInstallOldImageRemoved: "Önceki imaj silindi" diff --git a/core/i18n/lang/zh-Hant.yaml b/core/i18n/lang/zh-Hant.yaml index a5e8f4107..14775c682 100644 --- a/core/i18n/lang/zh-Hant.yaml +++ b/core/i18n/lang/zh-Hant.yaml @@ -587,6 +587,10 @@ MCPInstallRecoveryFailed: "安裝失敗且原服務還原不完整,請核對 MCPInstallLocalImage: "本機映像已存在,略過下載" MCPInstallVerifyImage: "驗證映像版本及受管契約" MCPInstallVerifiedImage: "映像驗證通過" + +PHPExtensionsNoTargets: "排除來源節點後,沒有可同步的目標節點" +PHPExtensionsSyncTask: "同步 PHP 擴充模版 [{{ .name }}]" +PHPExtensionsSyncSkipped: "節點 [{{ .node }}] 已存在相同模版,略過" MCPInstallCleanup: "正在清理舊容器" MCPInstallOldContainerRemoved: "舊容器已刪除" MCPInstallOldImageRemoved: "舊映像已刪除" diff --git a/core/i18n/lang/zh.yaml b/core/i18n/lang/zh.yaml index bede9f3e2..f6b184754 100644 --- a/core/i18n/lang/zh.yaml +++ b/core/i18n/lang/zh.yaml @@ -587,6 +587,10 @@ MCPInstallRecoveryFailed: "安装失败且原服务恢复不完整,请核对 MCPInstallLocalImage: "本地镜像已存在,跳过下载" MCPInstallVerifyImage: "校验镜像版本和受管契约" MCPInstallVerifiedImage: "镜像校验通过" + +PHPExtensionsNoTargets: "排除源节点后,没有可同步的目标节点" +PHPExtensionsSyncTask: "同步 PHP 扩展模版 [{{ .name }}]" +PHPExtensionsSyncSkipped: "节点 [{{ .node }}] 已存在相同模版,跳过" MCPInstallCleanup: "正在清理旧容器" MCPInstallOldContainerRemoved: "旧容器已删除" MCPInstallOldImageRemoved: "旧镜像已删除" diff --git a/core/init/router/proxy.go b/core/init/router/proxy.go index 174fb1aa8..a21bfba5d 100644 --- a/core/init/router/proxy.go +++ b/core/init/router/proxy.go @@ -199,5 +199,7 @@ func checkSession(c *gin.Context, refresh bool) bool { } func isLocalAPI(urlPath string) bool { - return urlPath == "/api/v2/core/xpack/sync/ssl" || urlPath == "/api/v2/core/xpack/settings/search" + return urlPath == "/api/v2/core/xpack/sync/ssl" || + urlPath == "/api/v2/core/xpack/sync/php/extensions" || + urlPath == "/api/v2/core/xpack/settings/search" } diff --git a/core/middleware/ip_limit.go b/core/middleware/ip_limit.go index 782360987..808013e98 100644 --- a/core/middleware/ip_limit.go +++ b/core/middleware/ip_limit.go @@ -98,7 +98,7 @@ func isLocalSyncRequest(reqPath, clientIP, token string) bool { } switch reqPath { - case "/api/v2/core/xpack/sync/ssl": + case "/api/v2/core/xpack/sync/ssl", "/api/v2/core/xpack/sync/php/extensions": return token != "" case "/api/v2/core/settings/ssl/reload": return token != "" diff --git a/frontend/src/api/interface/runtime.ts b/frontend/src/api/interface/runtime.ts index e7d390fa4..4cda6457a 100644 --- a/frontend/src/api/interface/runtime.ts +++ b/frontend/src/api/interface/runtime.ts @@ -1,6 +1,13 @@ import { CommonModel, ReqPage } from '.'; import { App } from './app'; export namespace Runtime { + export interface PHPExtensionsPush { + sourceNode: string; + targetNodes: string[]; + id: number; + taskID: string; + } + export interface Runtime extends CommonModel { name: string; appDetailID: number; diff --git a/frontend/src/api/modules/runtime.ts b/frontend/src/api/modules/runtime.ts index 5f74b2c65..f8ffcb575 100644 --- a/frontend/src/api/modules/runtime.ts +++ b/frontend/src/api/modules/runtime.ts @@ -46,8 +46,13 @@ export const OperateNodeModule = (req: Runtime.NodeModuleReq) => { return http.post(`/runtimes/node/modules/operate`, req, TimeoutEnum.T_10M); }; -export const SearchPHPExtensions = (req: ReqPage) => { - return http.post>(`/runtimes/php/extensions/search`, req); +export const SearchPHPExtensions = (req: ReqPage & { name?: string }, currentNode?: string) => { + return http.post>( + `/runtimes/php/extensions/search`, + req, + undefined, + currentNode ? { CurrentNode: currentNode } : undefined, + ); }; export const ListPHPExtensions = (req: Runtime.PHPExtensionsList) => { @@ -137,3 +142,7 @@ export const updateRemark = (req: Runtime.RemarkUpdate) => { export const getFPMStatus = (id: number) => { return http.get(`/runtimes/php/fpm/status/${id}`); }; + +export const PushPHPExtensions = (req: Runtime.PHPExtensionsPush) => { + return http.post(`/runtimes/php/extensions/push`, req, undefined, { CurrentNode: 'local' }); +}; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 3728394b5..5f5c3114d 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -4009,6 +4009,13 @@ const message = { nodeApp: 'Upgrade to Commercial Edition to upgrade multi-node app versions without switching nodes manually.', nodeDashboard: 'Upgrade to Commercial Edition for centralized management of multi-node apps, websites, databases, and scheduled tasks.', + imageExchange: 'Upgrade to Commercial Edition to transfer container images between nodes in batches.', + appExchange: + 'Upgrade to Commercial Edition to migrate installed applications to target nodes and confirm their resource dependencies.', + sslSync: + 'Upgrade to Commercial Edition to synchronize certificates from a source node to one or more target nodes.', + phpExtensionsSync: + 'Upgrade to Commercial Edition to synchronize PHP extension templates to one or more target nodes.', fileExchange: 'Upgrade to Commercial Edition to transfer files quickly between servers.', app: 'Upgrade to Commercial Edition to view service details, anomaly monitoring, and more in the mobile app.', cluster: 'Upgrade to Commercial Edition to manage MySQL/Postgres/Redis primary-replica clusters.', @@ -7036,6 +7043,35 @@ const message = { nodeContent3: "Real-time monitoring of each node's operational status", nodeTitle4: 'Quick Remote Connection', nodeContent4: 'One-click direct connection to node remote terminals', + imageExchangeTitle1: 'Select source images', + imageExchangeContent1: 'Browse container images on the source node and select the images to transfer.', + imageExchangeTitle2: 'Distribute images', + imageExchangeContent2: 'Transfer selected images to one or more target nodes.', + imageExchangeTitle3: 'View transfer logs', + imageExchangeContent3: 'Track image transfer progress and results in task logs.', + appExchangeTitle1: 'Select an application', + appExchangeContent1: 'Browse installed applications on the source node and select one to migrate.', + appExchangeTitle2: 'Confirm dependencies', + appExchangeContent2: 'Confirm target nodes and required resources, such as databases, before migration.', + appExchangeTitle3: 'Migrate applications', + appExchangeContent3: + 'Transfer application backups to target nodes for restoration and view results in task logs.', + sslSyncTitle1: 'Select certificates', + sslSyncContent1: 'Browse certificate details on the source node and select certificates to synchronize.', + sslSyncTitle2: 'Synchronize across nodes', + sslSyncContent2: + 'Synchronize selected certificates to target nodes, automatically excluding the source node.', + sslSyncTitle3: 'Confirm sync tasks', + sslSyncContent3: 'Review target nodes and certificates before execution, then view results in task logs.', + phpExtensionsSyncTitle1: 'Select a template', + phpExtensionsSyncContent1: + 'Browse template names and extension lists on the source node and select a template to synchronize.', + phpExtensionsSyncTitle2: 'Synchronize templates', + phpExtensionsSyncContent2: + 'Synchronize the selected template to target nodes, automatically excluding the source node.', + phpExtensionsSyncTitle3: 'Check existing templates', + phpExtensionsSyncContent3: + 'Skip templates with identical names and content. Report conflicts without overwriting different content.', fileExchangeTitle1: 'Key Authentication Transmission', fileExchangeContent1: 'Authenticate via SSH keys to ensure transmission security.', fileExchangeTitle2: 'Efficient File Synchronization', @@ -7550,6 +7586,13 @@ const message = { imageBatchConfirmPrefix: 'About to transfer {0} images to {1} nodes, for a total of {2} tasks.', ssl: 'Certificate Sync', + phpExtensions: 'PHP Extension Template Sync', + phpExtensionsTitle: 'Extension Template Sync', + phpExtensionsDesc: 'Select a source node to sync its PHP extension template to target nodes.', + phpExtensionsList: 'PHP extensions', + phpExtensionsConfirm: 'Sync {0} PHP extension template(s) to {1} node(s).', + phpExtensionsConflictHint: + 'Identical templates are skipped. Same-name templates with different content are reported as conflicts and will not be overwritten.', sslBatchDesc: 'After selecting a source node, sync the specified certificate into the current panel environment.', sslBatchConfirmPrefix: 'About to sync {0} certificates to {1} nodes, for a total of {2} tasks.', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 1dd1b6eb7..bf816a54a 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -7667,6 +7667,13 @@ const message = { imageBatchConfirmPrefix: 'Se enviarán {0} imágenes a {1} nodos, creando {2} tareas.', ssl: 'Sincronización de certificados', + phpExtensions: 'PHP Extension Template Sync', + phpExtensionsTitle: 'Extension Template Sync', + phpExtensionsDesc: 'Select a source node to sync its PHP extension template to target nodes.', + phpExtensionsList: 'PHP extensions', + phpExtensionsConfirm: 'Sync {0} PHP extension template(s) to {1} node(s).', + phpExtensionsConflictHint: + 'Identical templates are skipped. Same-name templates with different content are reported as conflicts and will not be overwritten.', sslBatchDesc: 'Después de seleccionar el nodo de origen, los certificados especificados pueden sincronizarse con el entorno actual del panel.', sslBatchConfirmPrefix: 'Se sincronizarán {0} certificados a {1} nodos, creando {2} tareas.', diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts index 6c4c1b8f0..f689c94b6 100644 --- a/frontend/src/lang/modules/fa.ts +++ b/frontend/src/lang/modules/fa.ts @@ -7508,6 +7508,13 @@ const message = { imageBatchConfirmPrefix: 'در حال انتقال {0} تصویر به {1} گره، مجموعاً {2} وظیفه.', ssl: 'همگام‌سازی گواهی', + phpExtensions: 'PHP Extension Template Sync', + phpExtensionsTitle: 'Extension Template Sync', + phpExtensionsDesc: 'Select a source node to sync its PHP extension template to target nodes.', + phpExtensionsList: 'PHP extensions', + phpExtensionsConfirm: 'Sync {0} PHP extension template(s) to {1} node(s).', + phpExtensionsConflictHint: + 'Identical templates are skipped. Same-name templates with different content are reported as conflicts and will not be overwritten.', sslBatchDesc: 'پس از انتخاب یک گره منبع، گواهی مشخص شده را به محیط پنل فعلی همگام‌سازی کنید.', sslBatchConfirmPrefix: 'در حال همگام‌سازی {0} گواهی به {1} گره، مجموعاً {2} وظیفه.', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 2d7a0a201..fc00479f8 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -7538,6 +7538,13 @@ const message = { '{0} 個のイメージを {1} 個のノードへ転送します。合計 {2} 件のタスクが作成されます。', ssl: '証明書同期', + phpExtensions: 'PHP Extension Template Sync', + phpExtensionsTitle: 'Extension Template Sync', + phpExtensionsDesc: 'Select a source node to sync its PHP extension template to target nodes.', + phpExtensionsList: 'PHP extensions', + phpExtensionsConfirm: 'Sync {0} PHP extension template(s) to {1} node(s).', + phpExtensionsConflictHint: + 'Identical templates are skipped. Same-name templates with different content are reported as conflicts and will not be overwritten.', sslBatchDesc: 'ソースノードを選択すると、指定した証明書を現在のパネル環境へ同期できます。', sslBatchConfirmPrefix: '{0} 件の証明書を {1} 個のノードへ同期します。合計 {2} 件のタスクが作成されます。', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 2f3bd9647..1e4a444a6 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -7390,6 +7390,13 @@ const message = { imageBatchConfirmPrefix: '{0}개의 이미지를 {1}개 노드로 전달합니다. 총 {2}개의 작업이 생성됩니다.', ssl: '인증서 동기화', + phpExtensions: 'PHP Extension Template Sync', + phpExtensionsTitle: 'Extension Template Sync', + phpExtensionsDesc: 'Select a source node to sync its PHP extension template to target nodes.', + phpExtensionsList: 'PHP extensions', + phpExtensionsConfirm: 'Sync {0} PHP extension template(s) to {1} node(s).', + phpExtensionsConflictHint: + 'Identical templates are skipped. Same-name templates with different content are reported as conflicts and will not be overwritten.', sslBatchDesc: '원본 노드를 선택한 후 지정된 인증서를 현재 패널 환경으로 동기화할 수 있습니다.', sslBatchConfirmPrefix: '{0}개의 인증서를 {1}개 노드로 동기화합니다. 총 {2}개의 작업이 생성됩니다.', diff --git a/frontend/src/lang/modules/lo.ts b/frontend/src/lang/modules/lo.ts index 6bb713c14..74b2f9a1e 100644 --- a/frontend/src/lang/modules/lo.ts +++ b/frontend/src/lang/modules/lo.ts @@ -7302,6 +7302,13 @@ const message = { imageBatchConfirmPrefix: 'ກຳລັງຈະໂອນ {0} images ໄປຫາ {1} ໂນດ, ລວມທັງໝົດ {2} ງານ.', ssl: 'ຊິງໂຄຣໄນໃບຢັ້ງຢືນ', + phpExtensions: 'PHP Extension Template Sync', + phpExtensionsTitle: 'Extension Template Sync', + phpExtensionsDesc: 'Select a source node to sync its PHP extension template to target nodes.', + phpExtensionsList: 'PHP extensions', + phpExtensionsConfirm: 'Sync {0} PHP extension template(s) to {1} node(s).', + phpExtensionsConflictHint: + 'Identical templates are skipped. Same-name templates with different content are reported as conflicts and will not be overwritten.', sslBatchDesc: 'ເລືອກໂນດຕົ້ນທາງ ແລະ ຊິງໂຄຣໄນໃບຢັ້ງຢືນທີ່ກຳນົດເຂົ້າມາໃນພາແນລປັດຈຸບັນ.', sslBatchConfirmPrefix: 'ກຳລັງຈະຊິງໂຄຣໄນ {0} ໃບຢັ້ງຢືນ ໄປຫາ {1} ໂນດ, ລວມທັງໝົດ {2} ງານ.', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 3cc1de022..e539764ae 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -7660,6 +7660,13 @@ const message = { imageBatchConfirmPrefix: 'Akan memindahkan {0} imej ke {1} nod, dengan jumlah {2} tugasan.', ssl: 'Penyegerakan Sijil', + phpExtensions: 'PHP Extension Template Sync', + phpExtensionsTitle: 'Extension Template Sync', + phpExtensionsDesc: 'Select a source node to sync its PHP extension template to target nodes.', + phpExtensionsList: 'PHP extensions', + phpExtensionsConfirm: 'Sync {0} PHP extension template(s) to {1} node(s).', + phpExtensionsConflictHint: + 'Identical templates are skipped. Same-name templates with different content are reported as conflicts and will not be overwritten.', sslBatchDesc: 'Selepas memilih nod sumber, sijil yang ditentukan boleh disegerakkan ke persekitaran panel semasa.', sslBatchConfirmPrefix: 'Akan menyegerakkan {0} sijil ke {1} nod, dengan jumlah {2} tugasan.', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 4435a56fb..103f23b1a 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -7697,6 +7697,13 @@ const message = { imageBatchConfirmPrefix: 'Serão encaminhadas {0} imagens para {1} nós, totalizando {2} tarefas.', ssl: 'Sincronização de Certificados', + phpExtensions: 'PHP Extension Template Sync', + phpExtensionsTitle: 'Extension Template Sync', + phpExtensionsDesc: 'Select a source node to sync its PHP extension template to target nodes.', + phpExtensionsList: 'PHP extensions', + phpExtensionsConfirm: 'Sync {0} PHP extension template(s) to {1} node(s).', + phpExtensionsConflictHint: + 'Identical templates are skipped. Same-name templates with different content are reported as conflicts and will not be overwritten.', sslBatchDesc: 'Após selecionar o nó de origem, os certificados especificados podem ser sincronizados com o ambiente atual do painel.', sslBatchConfirmPrefix: 'Serão sincronizados {0} certificados para {1} nós, totalizando {2} tarefas.', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 6a695e357..454f904de 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -7676,6 +7676,13 @@ const message = { imageBatchConfirmPrefix: 'Будет передано {0} образов на {1} узлов, всего {2} задач.', ssl: 'Синхронизация сертификатов', + phpExtensions: 'PHP Extension Template Sync', + phpExtensionsTitle: 'Extension Template Sync', + phpExtensionsDesc: 'Select a source node to sync its PHP extension template to target nodes.', + phpExtensionsList: 'PHP extensions', + phpExtensionsConfirm: 'Sync {0} PHP extension template(s) to {1} node(s).', + phpExtensionsConflictHint: + 'Identical templates are skipped. Same-name templates with different content are reported as conflicts and will not be overwritten.', sslBatchDesc: 'После выбора исходного узла указанные сертификаты можно синхронизировать с текущей средой панели.', sslBatchConfirmPrefix: 'Будет синхронизировано {0} сертификатов на {1} узлов, всего {2} задач.', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 3ef04d919..fedef9129 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -7673,6 +7673,13 @@ const message = { imageBatchConfirmPrefix: '{0} imaj {1} düğüme aktarılacak, toplam {2} görev oluşturulacak.', ssl: 'Sertifika Senkronizasyonu', + phpExtensions: 'PHP Extension Template Sync', + phpExtensionsTitle: 'Extension Template Sync', + phpExtensionsDesc: 'Select a source node to sync its PHP extension template to target nodes.', + phpExtensionsList: 'PHP extensions', + phpExtensionsConfirm: 'Sync {0} PHP extension template(s) to {1} node(s).', + phpExtensionsConflictHint: + 'Identical templates are skipped. Same-name templates with different content are reported as conflicts and will not be overwritten.', sslBatchDesc: 'Kaynak düğüm seçildikten sonra belirtilen sertifikalar mevcut panel ortamına senkronize edilebilir.', sslBatchConfirmPrefix: '{0} sertifika {1} düğüme senkronize edilecek, toplam {2} görev oluşturulacak.', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 32280df38..832e0298c 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -3747,6 +3747,10 @@ const message = { node: '升級商業版可以使用 1Panel 管理多台 Linux 伺服器。', nodeApp: '升級商業版可以統一升級多節點應用版本,無需手動切換節點。', nodeDashboard: '升級商業版可以集中管理多節點應用、網站、資料庫和計劃任務。', + imageExchange: '升級商業版可以在多個節點之間批次傳輸容器映像。', + appExchange: '升級商業版可以將已安裝的應用程式遷移到目標節點,並確認所需的關聯資源。', + sslSync: '升級商業版可以將來源節點的憑證同步到一個或多個目標節點。', + phpExtensionsSync: '升級商業版可以將 PHP 擴充範本同步到一個或多個目標節點。', fileExchange: '升級商業版可以在多台伺服器之間快速傳輸檔案。', app: '升級商業版可透過手機APP,檢視服務資訊、異常監控等。', cluster: '升級商業版可以管理 MySQL/Postgres/Redis 主從叢集。', @@ -6539,6 +6543,30 @@ const message = { nodeContent3: '即時掌握各節點執行狀態', nodeTitle4: '快速遠端連接', nodeContent4: '一鍵直連節點遠端終端', + imageExchangeTitle1: '選擇來源映像', + imageExchangeContent1: '檢視來源節點的容器映像,選擇需要傳輸的映像。', + imageExchangeTitle2: '批次分發映像', + imageExchangeContent2: '將選取的映像傳輸到一個或多個目標節點。', + imageExchangeTitle3: '檢視傳輸日誌', + imageExchangeContent3: '透過任務日誌檢視映像傳輸進度和執行結果。', + appExchangeTitle1: '選擇來源應用程式', + appExchangeContent1: '檢視來源節點已安裝的應用程式,選擇需要遷移的應用程式。', + appExchangeTitle2: '確認資源相依性', + appExchangeContent2: '遷移前確認目標節點及應用程式所需的資料庫等關聯資源。', + appExchangeTitle3: '執行應用程式遷移', + appExchangeContent3: '將應用程式備份傳輸到目標節點進行還原,並透過任務日誌檢視結果。', + sslSyncTitle1: '選擇來源憑證', + sslSyncContent1: '檢視來源節點的憑證資訊,選擇需要同步的憑證。', + sslSyncTitle2: '多節點憑證同步', + sslSyncContent2: '將選取的憑證同步到目標節點,自動排除來源節點。', + sslSyncTitle3: '確認同步任務', + sslSyncContent3: '執行前確認目標節點和憑證,透過任務日誌檢視同步結果。', + phpExtensionsSyncTitle1: '選擇擴充範本', + phpExtensionsSyncContent1: '檢視來源節點的範本名稱和擴充清單,選擇需要同步的範本。', + phpExtensionsSyncTitle2: '多節點範本同步', + phpExtensionsSyncContent2: '將選取的擴充範本同步到目標節點,自動排除來源節點。', + phpExtensionsSyncTitle3: '同名範本檢查', + phpExtensionsSyncContent3: '同名且內容一致的範本會略過;內容不同則提示衝突,不會覆寫。', fileExchangeTitle1: '金鑰認證傳輸', fileExchangeContent1: '通過 SSH 金鑰進行身份驗證,確保傳輸的安全性。', fileExchangeTitle2: '高效檔案同步', @@ -7022,6 +7050,12 @@ const message = { imageBatchConfirmPrefix: '即將把 {0} 張映像轉發到 {1} 個節點,共 {2} 個任務。', ssl: '證書同步', + phpExtensions: 'PHP 擴充模版同步', + phpExtensionsTitle: '擴充模版同步', + phpExtensionsDesc: '選擇來源節點後,可將指定 PHP 擴充模版同步至目標節點。', + phpExtensionsList: '擴充清單', + phpExtensionsConfirm: '即將把 {0} 個 PHP 擴充模版同步至 {1} 個目標節點。', + phpExtensionsConflictHint: '同名且內容一致的模版將略過;同名但內容不同的模版將提示衝突,不會覆蓋。', sslBatchDesc: '選擇來源節點後,可將指定證書同步到目前面板環境。', sslBatchConfirmPrefix: '即將把 {0} 張證書同步到 {1} 個節點,共 {2} 個任務。', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 8fda74df8..64ede0059 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -3777,6 +3777,10 @@ const message = { node: '升级商业版可以使用 1Panel 管理多台 linux 服务器。', nodeApp: '升级商业版可以统一升级多节点应用版本,无需手动切换节点。', nodeDashboard: '升级商业版可以集中管理多节点应用、网站、数据库和计划任务。', + imageExchange: '升级商业版可以在多个节点之间批量传输容器镜像。', + appExchange: '升级商业版可以将已安装的应用迁移到目标节点,并确认所需的关联资源。', + sslSync: '升级商业版可以将源节点的证书同步到一个或多个目标节点。', + phpExtensionsSync: '升级商业版可以将 PHP 扩展模版同步到一个或多个目标节点。', fileExchange: '升级商业版可以在多台服务器之间快速传输文件。', app: '升级商业版可通过手机 APP,查看服务信息、异常监控等。', cluster: '升级商业版可以管理 MySQL/Postgres/Redis 主从集群。', @@ -5494,6 +5498,30 @@ const message = { nodeTitle4: '快速远程连接', nodeContent4: '一键直连节点远程终端', + imageExchangeTitle1: '选择源镜像', + imageExchangeContent1: '查看源节点的容器镜像,选择需要传输的镜像。', + imageExchangeTitle2: '批量分发镜像', + imageExchangeContent2: '将选中的镜像传输到一个或多个目标节点。', + imageExchangeTitle3: '查看传输日志', + imageExchangeContent3: '通过任务日志查看镜像传输进度和执行结果。', + appExchangeTitle1: '选择源应用', + appExchangeContent1: '查看源节点已安装的应用,选择需要迁移的应用。', + appExchangeTitle2: '确认资源依赖', + appExchangeContent2: '迁移前确认目标节点及应用所需的数据库等关联资源。', + appExchangeTitle3: '执行应用迁移', + appExchangeContent3: '将应用备份传输到目标节点进行恢复,并通过任务日志查看结果。', + sslSyncTitle1: '选择源证书', + sslSyncContent1: '查看源节点的证书信息,选择需要同步的证书。', + sslSyncTitle2: '多节点证书同步', + sslSyncContent2: '将选中的证书同步到目标节点,自动排除源节点。', + sslSyncTitle3: '确认同步任务', + sslSyncContent3: '执行前确认目标节点和证书,通过任务日志查看同步结果。', + phpExtensionsSyncTitle1: '选择扩展模版', + phpExtensionsSyncContent1: '查看源节点的模版名称和扩展列表,选择需要同步的模版。', + phpExtensionsSyncTitle2: '多节点模版同步', + phpExtensionsSyncContent2: '将选中的扩展模版同步到目标节点,自动排除源节点。', + phpExtensionsSyncTitle3: '同名模版检查', + phpExtensionsSyncContent3: '同名且内容一致的模版会跳过;内容不同则提示冲突,不会覆盖。', fileExchangeTitle1: '密钥认证传输', fileExchangeContent1: '通过 SSH 密钥进行身份验证,确保传输的安全性。', fileExchangeTitle2: '高效文件同步', @@ -7079,6 +7107,12 @@ const message = { imageBatchConfirmPrefix: '即将把 {0} 张镜像转发到 {1} 个节点,共 {2} 个任务。', ssl: '证书同步', + phpExtensions: 'PHP 扩展模版同步', + phpExtensionsTitle: '扩展模版同步', + phpExtensionsDesc: '选择源节点后,可将指定 PHP 扩展模版同步到目标节点。', + phpExtensionsList: '扩展列表', + phpExtensionsConfirm: '即将把 {0} 个 PHP 扩展模版同步到 {1} 个目标节点。', + phpExtensionsConflictHint: '同名且内容一致的模版将跳过;同名但内容不同的模版将提示冲突,不会覆盖。', sslBatchDesc: '选择源节点后,可将指定证书同步到当前面板环境。', sslBatchConfirmPrefix: '即将把 {0} 张证书同步到 {1} 个节点,共 {2} 个任务。',