1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-02-28 19:14:13 +08:00

feat: Added progress display for image pulling (#7955)

This commit is contained in:
zhengkunwang 2025-02-24 10:42:51 +08:00 committed by GitHub
parent b80c55c59c
commit 4087a7af3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 99 additions and 29 deletions

View File

@ -612,6 +612,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
if err != nil { if err != nil {
return err return err
} }
dockerCLi, err := docker.NewClient()
if req.PullImage { if req.PullImage {
images, err := composeV2.GetDockerComposeImagesV2(content, []byte(detail.DockerCompose)) images, err := composeV2.GetDockerComposeImagesV2(content, []byte(detail.DockerCompose))
if err != nil { if err != nil {
@ -619,10 +620,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
} }
for _, image := range images { for _, image := range images {
t.Log(i18n.GetWithName("PullImageStart", image)) t.Log(i18n.GetWithName("PullImageStart", image))
if out, err := cmd.ExecWithTimeOut("docker pull "+image, 20*time.Minute); err != nil { if err = dockerCLi.PullImageWithProcess(t, image); err != nil {
if out != "" {
err = errors.New(out)
}
err = buserr.WithNameAndErr("ErrDockerPullImage", "", err) err = buserr.WithNameAndErr("ErrDockerPullImage", "", err)
return err return err
} }
@ -1046,6 +1044,10 @@ func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) error
return err return err
} }
imagePrefix := xpack.GetImagePrefix() imagePrefix := xpack.GetImagePrefix()
dockerCLi, err := docker.NewClient()
if err != nil {
return err
}
for _, image := range images { for _, image := range images {
if imagePrefix != "" { if imagePrefix != "" {
lastSlashIndex := strings.LastIndex(image, "/") lastSlashIndex := strings.LastIndex(image, "/")
@ -1054,17 +1056,19 @@ func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) error
} }
image = imagePrefix + "/" + image image = imagePrefix + "/" + image
} }
task.Log(i18n.GetWithName("PullImageStart", image)) task.Log(i18n.GetWithName("PullImageStart", image))
if out, err = cmd.ExecWithTimeOut("docker pull "+image, 20*time.Minute); err != nil { if err = dockerCLi.PullImageWithProcess(task, image); err != nil {
if out != "" { errOur := err.Error()
if strings.Contains(out, "no such host") { if errOur != "" {
if strings.Contains(errOur, "no such host") {
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":" errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
} }
if strings.Contains(out, "timeout") { if strings.Contains(errOur, "timeout") {
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":" errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
} }
} }
appInstall.Message = errMsg + out appInstall.Message = errMsg + errOur
installErr := errors.New(appInstall.Message) installErr := errors.New(appInstall.Message)
task.LogFailedWithErr(i18n.GetMsgByKey("PullImage"), installErr) task.LogFailedWithErr(i18n.GetMsgByKey("PullImage"), installErr)
return installErr return installErr

View File

@ -1375,7 +1375,7 @@ func (w WebsiteService) ChangePHPVersion(req request.WebsitePHPVersionReq) error
return buserr.New("ErrPHPResource") return buserr.New("ErrPHPResource")
} }
website.RuntimeID = req.RuntimeID website.RuntimeID = req.RuntimeID
phpProxy := fmt.Sprintf("127.0.0.1:%d", runtime.Port) phpProxy := fmt.Sprintf("127.0.0.1:%s", runtime.Port)
website.Proxy = phpProxy website.Proxy = phpProxy
server.UpdatePHPProxy([]string{website.Proxy}, "") server.UpdatePHPProxy([]string{website.Proxy}, "")
website.Type = constant.Runtime website.Type = constant.Runtime

View File

@ -849,7 +849,7 @@ func opWebsite(website *model.Website, operate string) error {
} }
server.UpdatePHPProxy([]string{website.Proxy}, localPath) server.UpdatePHPProxy([]string{website.Proxy}, localPath)
} else { } else {
proxy := fmt.Sprintf("http://127.0.0.1:%d", runtime.Port) proxy := fmt.Sprintf("http://127.0.0.1:%s", runtime.Port)
server.UpdateRootProxy([]string{proxy}) server.UpdateRootProxy([]string{proxy})
} }
} }

View File

@ -1,6 +1,7 @@
package task package task
import ( import (
"bufio"
"context" "context"
"fmt" "fmt"
"log" "log"
@ -26,6 +27,7 @@ type Task struct {
Name string Name string
TaskID string TaskID string
Logger *log.Logger Logger *log.Logger
Writer *bufio.Writer
SubTasks []*SubTask SubTasks []*SubTask
Rollbacks []RollbackFunc Rollbacks []RollbackFunc
logFile *os.File logFile *os.File
@ -111,6 +113,7 @@ func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, e
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open log file: %w", err) return nil, fmt.Errorf("failed to open log file: %w", err)
} }
writer := bufio.NewWriter(file)
logger := log.New(file, "", log.LstdFlags) logger := log.New(file, "", log.LstdFlags)
taskModel := &model.Task{ taskModel := &model.Task{
ID: taskID, ID: taskID,
@ -122,7 +125,7 @@ func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, e
Operate: operate, Operate: operate,
} }
taskRepo := repo.NewITaskRepo() taskRepo := repo.NewITaskRepo()
task := &Task{Name: name, logFile: file, Logger: logger, taskRepo: taskRepo, Task: taskModel} task := &Task{Name: name, logFile: file, Logger: logger, taskRepo: taskRepo, Task: taskModel, Writer: writer}
return task, nil return task, nil
} }

View File

@ -2,17 +2,21 @@ package docker
import ( import (
"context" "context"
"github.com/docker/docker/api/types/network" "encoding/json"
"fmt"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"io"
"os"
"strings"
"time"
) )
func NewDockerClient() (*client.Client, error) { func NewDockerClient() (*client.Client, error) {
@ -28,10 +32,6 @@ func NewDockerClient() (*client.Client, error) {
return cli, nil return cli, nil
} }
type Client struct {
cli *client.Client
}
func NewClient() (Client, error) { func NewClient() (Client, error) {
var settingItem model.Setting var settingItem model.Setting
_ = global.DB.Where("key = ?", "DockerSockPath").First(&settingItem).Error _ = global.DB.Where("key = ?", "DockerSockPath").First(&settingItem).Error
@ -48,10 +48,8 @@ func NewClient() (Client, error) {
}, nil }, nil
} }
func NewClientWithCli(cli *client.Client) (Client, error) { type Client struct {
return Client{ cli *client.Client
cli: cli,
}, nil
} }
func (c Client) Close() { func (c Client) Close() {
@ -142,6 +140,7 @@ func CreateDefaultDockerNetwork() error {
global.LOG.Errorf("init docker client error %s", err.Error()) global.LOG.Errorf("init docker client error %s", err.Error())
return err return err
} }
defer cli.Close() defer cli.Close()
if !cli.NetworkExist("1panel-network") { if !cli.NetworkExist("1panel-network") {
if err := cli.CreateNetwork("1panel-network"); err != nil { if err := cli.CreateNetwork("1panel-network"); err != nil {
@ -151,3 +150,66 @@ func CreateDefaultDockerNetwork() error {
} }
return nil return nil
} }
func setLog(id, newLastLine string, task *task.Task) error {
data, err := os.ReadFile(task.Task.LogFile)
if err != nil {
return fmt.Errorf("failed to read file: %v", err)
}
lines := strings.Split(string(data), "\n")
exist := false
for index, line := range lines {
if strings.Contains(line, id) {
lines[index] = newLastLine
exist = true
break
}
}
if !exist {
task.Log(newLastLine)
return nil
}
output := strings.Join(lines, "\n")
_ = os.WriteFile(task.Task.LogFile, []byte(output), os.ModePerm)
return nil
}
func (c Client) PullImageWithProcess(task *task.Task, imageName string) error {
out, err := c.cli.ImagePull(context.Background(), imageName, image.PullOptions{})
if err != nil {
return err
}
defer out.Close()
decoder := json.NewDecoder(out)
for {
var progress map[string]interface{}
if err = decoder.Decode(&progress); err != nil {
if err == io.EOF {
break
}
return err
}
timeStr := time.Now().Format("2006/01/02 15:04:05")
status, _ := progress["status"].(string)
if status == "Downloading" || status == "Extracting" {
id, _ := progress["id"].(string)
progressDetail, _ := progress["progressDetail"].(map[string]interface{})
current, _ := progressDetail["current"].(float64)
progressStr := ""
total, ok := progressDetail["total"].(float64)
if ok {
progressStr = fmt.Sprintf("%s %s [%s] --- %.2f%%", timeStr, status, id, (current/total)*100)
} else {
progressStr = fmt.Sprintf("%s %s [%s] --- %.2f%%", timeStr, status, id, current)
}
_ = setLog(id, progressStr, task)
}
if status == "Pull complete" || status == "Download complete" {
id, _ := progress["id"].(string)
progressStr := fmt.Sprintf("%s %s [%s] --- %.2f%%", timeStr, status, id, 100.0)
_ = setLog(id, progressStr, task)
}
}
return nil
}

View File

@ -126,6 +126,7 @@ var WebUrlMap = map[string]struct{}{
"/xpack/waf/websites": {}, "/xpack/waf/websites": {},
"/xpack/waf/log": {}, "/xpack/waf/log": {},
"/xpack/waf/block": {}, "/xpack/waf/block": {},
"/xpack/waf/blackwhite": {},
"/xpack/monitor/dashboard": {}, "/xpack/monitor/dashboard": {},
"/xpack/monitor/setting": {}, "/xpack/monitor/setting": {},
"/xpack/monitor/rank": {}, "/xpack/monitor/rank": {},

View File

@ -85,7 +85,7 @@ const search = async () => {
}; };
const openTaskLog = (row: Log.Task) => { const openTaskLog = (row: Log.Task) => {
taskLogRef.value.openWithTaskID(row.id, !(row.status == 'Executing')); taskLogRef.value.openWithTaskID(row.id, row.status == 'Executing');
}; };
onMounted(() => { onMounted(() => {

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="w-full h-full flex items-center justify-center px-8 py-6"> <div class="w-full h-full flex items-center justify-center px-8">
<div v-loading="loading" class="w-full flex-grow flex flex-col"> <div v-loading="loading" class="w-full flex-grow flex flex-col">
<div v-if="mfaShow"> <div v-if="mfaShow">
<el-form @submit.prevent> <el-form @submit.prevent>

View File

@ -9,7 +9,7 @@
class="bg-white shadow-lg relative z-10 border border-gray-200 flex overflow-hidden" class="bg-white shadow-lg relative z-10 border border-gray-200 flex overflow-hidden"
> >
<div class="grid grid-cols-1 md:grid-cols-2 items-stretch w-full h-full"> <div class="grid grid-cols-1 md:grid-cols-2 items-stretch w-full h-full">
<div v-if="showLogo" class="flex flex-col justify-center items-center w-full"> <div v-if="showLogo">
<img :src="logoImage" class="max-w-full max-h-full object-contain" /> <img :src="logoImage" class="max-w-full max-h-full object-contain" />
</div> </div>
<div :class="loginFormClass"> <div :class="loginFormClass">