diff --git a/agent/app/api/v2/image_remote.go b/agent/app/api/v2/image_remote.go new file mode 100644 index 000000000..763921907 --- /dev/null +++ b/agent/app/api/v2/image_remote.go @@ -0,0 +1,16 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/service" + "github.com/gin-gonic/gin" +) + +func (b *BaseApi) GetImageRemoteCheckStatus(c *gin.Context) { + status, err := service.GetImageRemoteCheckStatus() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, status) +} diff --git a/agent/app/service/container_image_check_update.go b/agent/app/service/container_image_check_update.go index c7b99c924..bf8347ebb 100644 --- a/agent/app/service/container_image_check_update.go +++ b/agent/app/service/container_image_check_update.go @@ -11,7 +11,6 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/task" - "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/i18n" "github.com/1Panel-dev/1Panel/agent/utils/docker" @@ -28,16 +27,15 @@ import ( const imageCheckUpdateSetting = "ContainerImageUpdateEnabled" type ImageCheckUpdateStatus struct { - ProxyRequired bool `json:"proxyRequired"` - TaskID string `json:"taskID"` - Enabled bool `json:"enabled"` - Running bool `json:"running"` - Checked int `json:"checked"` - Updates int `json:"updates"` - Failed int `json:"failed"` - Skipped int `json:"skipped"` - Error string `json:"error"` - FinishedAt int64 `json:"finishedAt"` + TaskID string `json:"taskID"` + Enabled bool `json:"enabled"` + Running bool `json:"running"` + Checked int `json:"checked"` + Updates int `json:"updates"` + Failed int `json:"failed"` + Skipped int `json:"skipped"` + Error string `json:"error"` + FinishedAt int64 `json:"finishedAt"` } var imageCheckUpdateState struct { @@ -50,23 +48,10 @@ func GetContainerImageCheckUpdateStatus() (ImageCheckUpdateStatus, error) { if err := global.DB.Where("key = ?", imageCheckUpdateSetting).First(&setting).Error; err != nil { return ImageCheckUpdateStatus{}, err } - httpClient, proxyErr := dockerhub.NewHTTPClient() - proxyRequired := false - if proxyErr != nil { - if business, ok := proxyErr.(buserr.BusinessError); ok && business.Msg == "ErrImageCheckProxyRequired" { - proxyRequired = true - } else { - return ImageCheckUpdateStatus{}, proxyErr - } - } - if httpClient != nil && httpClient.Transport != nil { - httpClient.CloseIdleConnections() - } imageCheckUpdateState.Lock() defer imageCheckUpdateState.Unlock() status := imageCheckUpdateState.status status.Enabled = setting.Value == "Enable" - status.ProxyRequired = proxyRequired return status, nil } func SetContainerImageCheckUpdateSchedule(enabled bool) error { diff --git a/agent/app/service/image_remote.go b/agent/app/service/image_remote.go new file mode 100644 index 000000000..0f4cc64ae --- /dev/null +++ b/agent/app/service/image_remote.go @@ -0,0 +1,29 @@ +package service + +import ( + "errors" + + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/utils/dockerhub" +) + +type ImageRemoteCheckStatus struct { + ProxyRequired bool `json:"proxyRequired"` +} + +// GetImageRemoteCheckStatus validates local settings without contacting Docker Hub. +// Both image searches and update checks use the same proxy policy. +func GetImageRemoteCheckStatus() (ImageRemoteCheckStatus, error) { + client, err := dockerhub.NewHTTPClient() + if err != nil { + var business buserr.BusinessError + if errors.As(err, &business) && business.Msg == "ErrImageCheckProxyRequired" { + return ImageRemoteCheckStatus{ProxyRequired: true}, nil + } + return ImageRemoteCheckStatus{}, err + } + if client.Transport != nil { + client.CloseIdleConnections() + } + return ImageRemoteCheckStatus{}, nil +} diff --git a/agent/router/ro_container.go b/agent/router/ro_container.go index b8da50cdf..cb351e5aa 100644 --- a/agent/router/ro_container.go +++ b/agent/router/ro_container.go @@ -67,6 +67,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) { baRouter.POST("/template", baseApi.CreateComposeTemplate) baRouter.POST("/template/del", baseApi.DeleteComposeTemplate) + baRouter.GET("/image/remote/check", baseApi.GetImageRemoteCheckStatus) baRouter.GET("/image", baseApi.ListImage) baRouter.GET("/image/all", baseApi.ListAllImage) baRouter.POST("/image/search", baseApi.SearchImage) diff --git a/frontend/src/api/modules/container.ts b/frontend/src/api/modules/container.ts index 3d546d9f7..7e49b763a 100644 --- a/frontend/src/api/modules/container.ts +++ b/frontend/src/api/modules/container.ts @@ -291,8 +291,17 @@ export const updateDaemonJsonByfile = (params: Container.DaemonJsonUpdateByFile) return http.post(`/containers/daemonjson/update/byfile`, params, TimeoutEnum.T_3M); }; -export interface ContainerImageCheckUpdateStatus { +export interface ImageRemoteCheckStatus { proxyRequired: boolean; +} +export const getImageRemoteCheckStatus = (node: string, signal?: AbortSignal) => + http.get( + '/containers/image/remote/check', + {}, + { headers: { CurrentNode: node }, signal }, + ); + +export interface ContainerImageCheckUpdateStatus { taskID: string; enabled: boolean; running: boolean; diff --git a/frontend/src/views/container/container/index.vue b/frontend/src/views/container/container/index.vue index c1a2112f0..0224195f5 100644 --- a/frontend/src/views/container/container/index.vue +++ b/frontend/src/views/container/container/index.vue @@ -505,6 +505,7 @@ import { ElMessageBox } from 'element-plus'; import { ArrowDown } from '@element-plus/icons-vue'; import { getContainerImageCheckUpdateStatus, + getImageRemoteCheckStatus, runContainerImageCheckUpdate, setContainerImageCheckUpdateSchedule, type ContainerImageCheckUpdateStatus, @@ -878,7 +879,10 @@ const onImageCheckUpdateCommand = async (command: string) => { if (generation !== imageCheckUpdateGeneration || node !== currentNode.value) return; imageCheckUpdateStatus.value = status.data; // Disabling an existing schedule must remain possible without a proxy. - if ((command === 'check' || !status.data.enabled) && status.data.proxyRequired) { + const proxyStatus = + command === 'check' || !status.data.enabled ? await getImageRemoteCheckStatus(node) : undefined; + if (generation !== imageCheckUpdateGeneration || node !== currentNode.value) return; + if (proxyStatus?.data.proxyRequired) { try { await ElMessageBox.confirm(i18n.global.t('container.imageCheckUpdateProxyRequired'), '', { confirmButtonText: i18n.global.t('container.imageCheckUpdateConfigureProxy'),