feat(container): add image update checks with scheduling and task logs

This commit is contained in:
guoyuqi
2026-10-29 19:02:44 +08:00
parent e9c2162921
commit 8b6328fd04
6 changed files with 70 additions and 26 deletions
+16
View File
@@ -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)
}
@@ -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 {
+29
View File
@@ -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
}
+1
View File
@@ -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)
+10 -1
View File
@@ -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<ImageRemoteCheckStatus>(
'/containers/image/remote/check',
{},
{ headers: { CurrentNode: node }, signal },
);
export interface ContainerImageCheckUpdateStatus {
taskID: string;
enabled: boolean;
running: boolean;
@@ -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'),