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:
parent
b80c55c59c
commit
4087a7af3c
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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": {},
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user