From e58e5cef0de49b32a41a7f616a31e673b25f1eb0 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Tue, 18 Oct 2022 18:39:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E7=BB=88=E7=AB=AF=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/compose_template.go | 8 +- backend/app/api/v1/container.go | 68 +++- backend/app/api/v1/image.go | 8 +- backend/app/api/v1/image_repo.go | 8 +- backend/app/dto/container.go | 1 + backend/app/repo/compose_template.go | 4 +- backend/app/repo/image_repo.go | 4 +- backend/app/service/app_utils.go | 19 +- backend/app/service/compose_template.go | 4 +- backend/app/service/container.go | 318 +----------------- backend/app/service/container_compose.go | 149 ++++++++ backend/app/service/container_network.go | 106 ++++++ backend/app/service/container_volume.go | 98 ++++++ backend/app/service/entry.go | 1 - backend/app/service/image.go | 8 +- backend/app/service/image_repo.go | 4 +- backend/app/service/image_test.go | 115 ------- backend/router/ro_container.go | 6 +- .../utils/cloud_storage/client/minio_test.go | 12 +- .../utils/cloud_storage/client/oss_test.go | 12 +- .../utils/cloud_storage/client/sftp_test.go | 12 +- backend/utils/terminal/exec.go | 102 ++++++ backend/utils/terminal/ws_session.go | 1 + frontend/src/api/interface/container.ts | 2 + .../app-layout/menu/components/sub-item.vue | 5 + frontend/src/lang/modules/en.ts | 4 + frontend/src/lang/modules/zh.ts | 4 + frontend/src/routers/modules/container.ts | 6 - .../src/views/container/compose/index.vue | 9 +- .../container/compose/operator/index.vue | 4 +- .../src/views/container/container/index.vue | 19 ++ .../container/container/terminal/index.vue | 207 ++++++++++++ frontend/src/views/container/index.vue | 2 +- .../src/views/container/network/index.vue | 11 +- go.mod | 1 - go.sum | 40 +-- 36 files changed, 848 insertions(+), 534 deletions(-) create mode 100644 backend/app/service/container_compose.go create mode 100644 backend/app/service/container_network.go create mode 100644 backend/app/service/container_volume.go delete mode 100644 backend/app/service/image_test.go create mode 100644 backend/utils/terminal/exec.go create mode 100644 frontend/src/views/container/container/terminal/index.vue diff --git a/backend/app/api/v1/compose_template.go b/backend/app/api/v1/compose_template.go index 03be6b7b1..75f1ea8a3 100644 --- a/backend/app/api/v1/compose_template.go +++ b/backend/app/api/v1/compose_template.go @@ -1,10 +1,10 @@ package v1 import ( - "github.com/1Panel-dev/1Panel/app/api/v1/helper" - "github.com/1Panel-dev/1Panel/app/dto" - "github.com/1Panel-dev/1Panel/constant" - "github.com/1Panel-dev/1Panel/global" + "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" "github.com/gin-gonic/gin" ) diff --git a/backend/app/api/v1/container.go b/backend/app/api/v1/container.go index 445f75d75..d6acae97d 100644 --- a/backend/app/api/v1/container.go +++ b/backend/app/api/v1/container.go @@ -1,13 +1,18 @@ package v1 import ( - "errors" + "context" + "strconv" - "github.com/1Panel-dev/1Panel/app/api/v1/helper" - "github.com/1Panel-dev/1Panel/app/dto" - "github.com/1Panel-dev/1Panel/constant" - "github.com/1Panel-dev/1Panel/global" + "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/utils/docker" + "github.com/1Panel-dev/1Panel/backend/utils/terminal" + "github.com/docker/docker/api/types" "github.com/gin-gonic/gin" + "github.com/pkg/errors" ) func (b *BaseApi) SearchContainer(c *gin.Context) { @@ -158,6 +163,59 @@ func (b *BaseApi) Inspect(c *gin.Context) { helper.SuccessWithData(c, result) } +func (b *BaseApi) ContainerExec(c *gin.Context) { + containerID := c.Query("containerid") + command := c.Query("command") + user := c.Query("user") + cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + global.LOG.Errorf("gin context http handler failed, err: %v", err) + return + } + defer wsConn.Close() + + client, err := docker.NewDockerClient() + if wshandleError(wsConn, errors.WithMessage(err, "New docker client failed.")) { + return + } + conf := types.ExecConfig{Tty: true, Cmd: []string{command}, AttachStderr: true, AttachStdin: true, AttachStdout: true, User: user} + ir, err := client.ContainerExecCreate(context.TODO(), containerID, conf) + if wshandleError(wsConn, errors.WithMessage(err, "failed to set exec conf.")) { + return + } + hr, err := client.ContainerExecAttach(c, ir.ID, types.ExecStartCheck{Detach: false, Tty: true}) + if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection.")) { + return + } + defer hr.Close() + + sws, err := terminal.NewExecConn(cols, rows, wsConn, hr.Conn) + if wshandleError(wsConn, err) { + return + } + + quitChan := make(chan bool, 3) + ctx, cancel := context.WithCancel(context.Background()) + sws.Start(ctx, quitChan) + <-quitChan + cancel() + + if wshandleError(wsConn, err) { + return + } +} + func (b *BaseApi) ContainerLogs(c *gin.Context) { var req dto.ContainerLog if err := c.ShouldBindJSON(&req); err != nil { diff --git a/backend/app/api/v1/image.go b/backend/app/api/v1/image.go index 2bbcfae3a..f9f05488a 100644 --- a/backend/app/api/v1/image.go +++ b/backend/app/api/v1/image.go @@ -1,10 +1,10 @@ package v1 import ( - "github.com/1Panel-dev/1Panel/app/api/v1/helper" - "github.com/1Panel-dev/1Panel/app/dto" - "github.com/1Panel-dev/1Panel/constant" - "github.com/1Panel-dev/1Panel/global" + "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" "github.com/gin-gonic/gin" ) diff --git a/backend/app/api/v1/image_repo.go b/backend/app/api/v1/image_repo.go index b701f385f..78ae5c140 100644 --- a/backend/app/api/v1/image_repo.go +++ b/backend/app/api/v1/image_repo.go @@ -1,10 +1,10 @@ package v1 import ( - "github.com/1Panel-dev/1Panel/app/api/v1/helper" - "github.com/1Panel-dev/1Panel/app/dto" - "github.com/1Panel-dev/1Panel/constant" - "github.com/1Panel-dev/1Panel/global" + "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" "github.com/gin-gonic/gin" ) diff --git a/backend/app/dto/container.go b/backend/app/dto/container.go index 9d49fdac7..a13a86b06 100644 --- a/backend/app/dto/container.go +++ b/backend/app/dto/container.go @@ -112,6 +112,7 @@ type BatchDelete struct { type ComposeInfo struct { Name string `json:"name"` CreatedAt string `json:"createdAt"` + CreatedBy string `json:"createdBy"` ContainerNumber int `json:"containerNumber"` ConfigFile string `json:"configFile"` Workdir string `json:"workdir"` diff --git a/backend/app/repo/compose_template.go b/backend/app/repo/compose_template.go index ac1daf08f..f0bd27864 100644 --- a/backend/app/repo/compose_template.go +++ b/backend/app/repo/compose_template.go @@ -1,8 +1,8 @@ package repo import ( - "github.com/1Panel-dev/1Panel/app/model" - "github.com/1Panel-dev/1Panel/global" + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/global" ) type ComposeTemplateRepo struct{} diff --git a/backend/app/repo/image_repo.go b/backend/app/repo/image_repo.go index 27cc52026..79e7e95c9 100644 --- a/backend/app/repo/image_repo.go +++ b/backend/app/repo/image_repo.go @@ -1,8 +1,8 @@ package repo import ( - "github.com/1Panel-dev/1Panel/app/model" - "github.com/1Panel-dev/1Panel/global" + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/global" ) type ImageRepoRepo struct{} diff --git a/backend/app/service/app_utils.go b/backend/app/service/app_utils.go index a67b550b8..a46ad3695 100644 --- a/backend/app/service/app_utils.go +++ b/backend/app/service/app_utils.go @@ -4,6 +4,16 @@ import ( "context" "encoding/json" "fmt" + "io/ioutil" + "math" + "net/http" + "os" + "path" + "reflect" + "strconv" + "strings" + "time" + "github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/constant" @@ -15,15 +25,6 @@ import ( "github.com/joho/godotenv" "github.com/pkg/errors" "gopkg.in/yaml.v3" - "io/ioutil" - "math" - "net/http" - "os" - "path" - "reflect" - "strconv" - "strings" - "time" ) type DatabaseOp string diff --git a/backend/app/service/compose_template.go b/backend/app/service/compose_template.go index 6d0daeca7..e75a7f73d 100644 --- a/backend/app/service/compose_template.go +++ b/backend/app/service/compose_template.go @@ -1,8 +1,8 @@ package service import ( - "github.com/1Panel-dev/1Panel/app/dto" - "github.com/1Panel-dev/1Panel/constant" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" "github.com/jinzhu/copier" "github.com/pkg/errors" ) diff --git a/backend/app/service/container.go b/backend/app/service/container.go index 4df1419cd..df67ff999 100644 --- a/backend/app/service/container.go +++ b/backend/app/service/container.go @@ -1,28 +1,23 @@ package service import ( - "bufio" "bytes" "context" "encoding/json" "fmt" "io" "io/ioutil" - "os" - "os/exec" "strconv" "strings" "time" - "github.com/1Panel-dev/1Panel/app/dto" - "github.com/1Panel-dev/1Panel/constant" - "github.com/1Panel-dev/1Panel/global" - "github.com/1Panel-dev/1Panel/utils/docker" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/utils/docker" "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/network" - "github.com/docker/docker/api/types/volume" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/go-connections/nat" v1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -30,10 +25,6 @@ import ( type ContainerService struct{} -const composeProjectLabel = "com.docker.compose.project" -const composeConfigLabel = "com.docker.compose.project.config_files" -const composeWorkdirLabel = "com.docker.compose.project.working_dir" - type IContainerService interface { Page(req dto.PageContainer) (int64, interface{}, error) PageNetwork(req dto.PageInfo) (int64, interface{}, error) @@ -101,128 +92,6 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro return int64(total), backDatas, nil } -func (u *ContainerService) PageCompose(req dto.PageInfo) (int64, interface{}, error) { - var ( - records []dto.ComposeInfo - BackDatas []dto.ComposeInfo - ) - client, err := docker.NewDockerClient() - if err != nil { - return 0, nil, err - } - - options := types.ContainerListOptions{All: true} - options.Filters = filters.NewArgs() - options.Filters.Add("label", composeProjectLabel) - - list, err := client.ContainerList(context.Background(), options) - if err != nil { - return 0, nil, err - } - composeMap := make(map[string]dto.ComposeInfo) - for _, container := range list { - if name, ok := container.Labels[composeProjectLabel]; ok { - containerItem := dto.ComposeContainer{ - ContainerID: container.ID, - Name: container.Names[0][1:], - State: container.State, - CreateTime: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"), - } - if compose, has := composeMap[name]; has { - compose.ContainerNumber++ - compose.Containers = append(compose.Containers, containerItem) - composeMap[name] = compose - } else { - config := container.Labels[composeConfigLabel] - workdir := container.Labels[composeWorkdirLabel] - composeItem := dto.ComposeInfo{ - ContainerNumber: 1, - CreatedAt: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"), - ConfigFile: config, - Workdir: workdir, - Containers: []dto.ComposeContainer{containerItem}, - } - if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) { - composeItem.Path = config - } else { - composeItem.Path = workdir - } - composeMap[name] = composeItem - } - } - } - for key, value := range composeMap { - value.Name = key - records = append(records, value) - } - total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize - if start > total { - BackDatas = make([]dto.ComposeInfo, 0) - } else { - if end >= total { - end = total - } - BackDatas = records[start:end] - } - return int64(total), BackDatas, nil -} - -func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error { - if req.From == "template" { - template, err := composeRepo.Get(commonRepo.WithByID(req.Template)) - if err != nil { - return err - } - req.From = template.From - if req.From == "edit" { - req.File = template.Content - } else { - req.Path = template.Path - } - } - if req.From == "edit" { - dir := fmt.Sprintf("%s/%s", constant.TmpComposeBuildDir, req.Name) - if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { - if err = os.MkdirAll(dir, os.ModePerm); err != nil { - return err - } - } - - path := fmt.Sprintf("%s/docker-compose.yml", dir) - file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) - if err != nil { - return err - } - defer file.Close() - write := bufio.NewWriter(file) - _, _ = write.WriteString(string(req.File)) - write.Flush() - req.Path = path - } - go func() { - cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d") - stdout, err := cmd.CombinedOutput() - if err != nil { - global.LOG.Debugf("docker-compose up %s failed, err: %v", req.Name, err) - return - } - global.LOG.Debugf("docker-compose up %s successful, logs: %v", req.Name, string(stdout)) - }() - - return nil -} - -func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error { - cmd := exec.Command("docker-compose", "-f", req.Path, req.Operation) - stdout, err := cmd.CombinedOutput() - if err != nil { - return err - } - global.LOG.Debugf("docker-compose %s %s successful: logs: %v", req.Operation, req.Path, string(stdout)) - - return err -} - func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) { client, err := docker.NewDockerClient() if err != nil { @@ -387,187 +256,6 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro return &data, nil } -func (u *ContainerService) PageNetwork(req dto.PageInfo) (int64, interface{}, error) { - client, err := docker.NewDockerClient() - if err != nil { - return 0, nil, err - } - list, err := client.NetworkList(context.TODO(), types.NetworkListOptions{}) - if err != nil { - return 0, nil, err - } - var ( - data []dto.Network - records []types.NetworkResource - ) - total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize - if start > total { - records = make([]types.NetworkResource, 0) - } else { - if end >= total { - end = total - } - records = list[start:end] - } - - for _, item := range records { - tag := make([]string, 0) - for key, val := range item.Labels { - tag = append(tag, fmt.Sprintf("%s=%s", key, val)) - } - var ipam network.IPAMConfig - if len(item.IPAM.Config) > 0 { - ipam = item.IPAM.Config[0] - } - data = append(data, dto.Network{ - ID: item.ID, - CreatedAt: item.Created, - Name: item.Name, - Driver: item.Driver, - IPAMDriver: item.IPAM.Driver, - Subnet: ipam.Subnet, - Gateway: ipam.Gateway, - Attachable: item.Attachable, - Labels: tag, - }) - } - - return int64(total), data, nil -} -func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error { - client, err := docker.NewDockerClient() - if err != nil { - return err - } - for _, id := range req.Ids { - if err := client.NetworkRemove(context.TODO(), id); err != nil { - return err - } - } - return nil -} -func (u *ContainerService) CreateNetwork(req dto.NetworkCreat) error { - client, err := docker.NewDockerClient() - if err != nil { - return err - } - var ( - ipam network.IPAMConfig - hasConf bool - ) - if len(req.Subnet) != 0 { - ipam.Subnet = req.Subnet - hasConf = true - } - if len(req.Gateway) != 0 { - ipam.Gateway = req.Gateway - hasConf = true - } - if len(req.IPRange) != 0 { - ipam.IPRange = req.IPRange - hasConf = true - } - - options := types.NetworkCreate{ - Driver: req.Driver, - Options: stringsToMap(req.Options), - Labels: stringsToMap(req.Labels), - } - if hasConf { - options.IPAM = &network.IPAM{Config: []network.IPAMConfig{ipam}} - } - if _, err := client.NetworkCreate(context.TODO(), req.Name, options); err != nil { - return err - } - return nil -} - -func (u *ContainerService) PageVolume(req dto.PageInfo) (int64, interface{}, error) { - client, err := docker.NewDockerClient() - if err != nil { - return 0, nil, err - } - list, err := client.VolumeList(context.TODO(), filters.NewArgs()) - if err != nil { - return 0, nil, err - } - var ( - data []dto.Volume - records []*types.Volume - ) - total, start, end := len(list.Volumes), (req.Page-1)*req.PageSize, req.Page*req.PageSize - if start > total { - records = make([]*types.Volume, 0) - } else { - if end >= total { - end = total - } - records = list.Volumes[start:end] - } - - for _, item := range records { - tag := make([]string, 0) - for _, val := range item.Labels { - tag = append(tag, val) - } - createTime, _ := time.Parse("2006-01-02T15:04:05Z", item.CreatedAt) - data = append(data, dto.Volume{ - CreatedAt: createTime, - Name: item.Name, - Driver: item.Driver, - Mountpoint: item.Mountpoint, - Labels: tag, - }) - } - - return int64(total), data, nil -} -func (u *ContainerService) ListVolume() ([]dto.Options, error) { - client, err := docker.NewDockerClient() - if err != nil { - return nil, err - } - list, err := client.VolumeList(context.TODO(), filters.NewArgs()) - if err != nil { - return nil, err - } - var data []dto.Options - for _, item := range list.Volumes { - data = append(data, dto.Options{ - Option: item.Name, - }) - } - return data, nil -} -func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error { - client, err := docker.NewDockerClient() - if err != nil { - return err - } - for _, id := range req.Ids { - if err := client.VolumeRemove(context.TODO(), id, true); err != nil { - return err - } - } - return nil -} -func (u *ContainerService) CreateVolume(req dto.VolumeCreat) error { - client, err := docker.NewDockerClient() - if err != nil { - return err - } - options := volume.VolumeCreateBody{ - Name: req.Name, - Driver: req.Driver, - DriverOpts: stringsToMap(req.Options), - Labels: stringsToMap(req.Labels), - } - if _, err := client.VolumeCreate(context.TODO(), options); err != nil { - return err - } - return nil -} - func stringsToMap(list []string) map[string]string { var lableMap = make(map[string]string) for _, label := range list { diff --git a/backend/app/service/container_compose.go b/backend/app/service/container_compose.go new file mode 100644 index 000000000..5d9cb205e --- /dev/null +++ b/backend/app/service/container_compose.go @@ -0,0 +1,149 @@ +package service + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/utils/docker" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "golang.org/x/net/context" +) + +const composeProjectLabel = "com.docker.compose.project" +const composeConfigLabel = "com.docker.compose.project.config_files" +const composeWorkdirLabel = "com.docker.compose.project.working_dir" +const composeCreatedBy = "createdBy" + +func (u *ContainerService) PageCompose(req dto.PageInfo) (int64, interface{}, error) { + var ( + records []dto.ComposeInfo + BackDatas []dto.ComposeInfo + ) + client, err := docker.NewDockerClient() + if err != nil { + return 0, nil, err + } + + options := types.ContainerListOptions{All: true} + options.Filters = filters.NewArgs() + options.Filters.Add("label", composeProjectLabel) + + list, err := client.ContainerList(context.Background(), options) + if err != nil { + return 0, nil, err + } + composeMap := make(map[string]dto.ComposeInfo) + for _, container := range list { + if name, ok := container.Labels[composeProjectLabel]; ok { + containerItem := dto.ComposeContainer{ + ContainerID: container.ID, + Name: container.Names[0][1:], + State: container.State, + CreateTime: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"), + } + if compose, has := composeMap[name]; has { + compose.ContainerNumber++ + compose.Containers = append(compose.Containers, containerItem) + composeMap[name] = compose + } else { + config := container.Labels[composeConfigLabel] + workdir := container.Labels[composeWorkdirLabel] + composeItem := dto.ComposeInfo{ + ContainerNumber: 1, + CreatedAt: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"), + ConfigFile: config, + Workdir: workdir, + Containers: []dto.ComposeContainer{containerItem}, + } + createdBy, ok := container.Labels[composeCreatedBy] + if ok { + composeItem.CreatedBy = createdBy + } + if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) { + composeItem.Path = config + } else { + composeItem.Path = workdir + } + composeMap[name] = composeItem + } + } + } + for key, value := range composeMap { + value.Name = key + records = append(records, value) + } + total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + BackDatas = make([]dto.ComposeInfo, 0) + } else { + if end >= total { + end = total + } + BackDatas = records[start:end] + } + return int64(total), BackDatas, nil +} + +func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error { + if req.From == "template" { + template, err := composeRepo.Get(commonRepo.WithByID(req.Template)) + if err != nil { + return err + } + req.From = template.From + if req.From == "edit" { + req.File = template.Content + } else { + req.Path = template.Path + } + } + if req.From == "edit" { + dir := fmt.Sprintf("%s/%s", constant.TmpComposeBuildDir, req.Name) + if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + } + + path := fmt.Sprintf("%s/docker-compose.yml", dir) + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(string(req.File)) + write.Flush() + req.Path = path + } + go func() { + cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d") + stdout, err := cmd.CombinedOutput() + if err != nil { + global.LOG.Debugf("docker-compose up %s failed, err: %v", req.Name, err) + return + } + global.LOG.Debugf("docker-compose up %s successful, logs: %v", req.Name, string(stdout)) + }() + + return nil +} + +func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error { + cmd := exec.Command("docker-compose", "-f", req.Path, req.Operation) + stdout, err := cmd.CombinedOutput() + if err != nil { + return err + } + global.LOG.Debugf("docker-compose %s %s successful: logs: %v", req.Operation, req.Path, string(stdout)) + + return err +} diff --git a/backend/app/service/container_network.go b/backend/app/service/container_network.go new file mode 100644 index 000000000..67396813e --- /dev/null +++ b/backend/app/service/container_network.go @@ -0,0 +1,106 @@ +package service + +import ( + "context" + "fmt" + + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/utils/docker" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/network" +) + +func (u *ContainerService) PageNetwork(req dto.PageInfo) (int64, interface{}, error) { + client, err := docker.NewDockerClient() + if err != nil { + return 0, nil, err + } + list, err := client.NetworkList(context.TODO(), types.NetworkListOptions{}) + if err != nil { + return 0, nil, err + } + var ( + data []dto.Network + records []types.NetworkResource + ) + total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + records = make([]types.NetworkResource, 0) + } else { + if end >= total { + end = total + } + records = list[start:end] + } + + for _, item := range records { + tag := make([]string, 0) + for key, val := range item.Labels { + tag = append(tag, fmt.Sprintf("%s=%s", key, val)) + } + var ipam network.IPAMConfig + if len(item.IPAM.Config) > 0 { + ipam = item.IPAM.Config[0] + } + data = append(data, dto.Network{ + ID: item.ID, + CreatedAt: item.Created, + Name: item.Name, + Driver: item.Driver, + IPAMDriver: item.IPAM.Driver, + Subnet: ipam.Subnet, + Gateway: ipam.Gateway, + Attachable: item.Attachable, + Labels: tag, + }) + } + + return int64(total), data, nil +} +func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + for _, id := range req.Ids { + if err := client.NetworkRemove(context.TODO(), id); err != nil { + return err + } + } + return nil +} +func (u *ContainerService) CreateNetwork(req dto.NetworkCreat) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + var ( + ipam network.IPAMConfig + hasConf bool + ) + if len(req.Subnet) != 0 { + ipam.Subnet = req.Subnet + hasConf = true + } + if len(req.Gateway) != 0 { + ipam.Gateway = req.Gateway + hasConf = true + } + if len(req.IPRange) != 0 { + ipam.IPRange = req.IPRange + hasConf = true + } + + options := types.NetworkCreate{ + Driver: req.Driver, + Options: stringsToMap(req.Options), + Labels: stringsToMap(req.Labels), + } + if hasConf { + options.IPAM = &network.IPAM{Config: []network.IPAMConfig{ipam}} + } + if _, err := client.NetworkCreate(context.TODO(), req.Name, options); err != nil { + return err + } + return nil +} diff --git a/backend/app/service/container_volume.go b/backend/app/service/container_volume.go new file mode 100644 index 000000000..e1a569dbf --- /dev/null +++ b/backend/app/service/container_volume.go @@ -0,0 +1,98 @@ +package service + +import ( + "context" + "time" + + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/utils/docker" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/volume" +) + +func (u *ContainerService) PageVolume(req dto.PageInfo) (int64, interface{}, error) { + client, err := docker.NewDockerClient() + if err != nil { + return 0, nil, err + } + list, err := client.VolumeList(context.TODO(), filters.NewArgs()) + if err != nil { + return 0, nil, err + } + var ( + data []dto.Volume + records []*types.Volume + ) + total, start, end := len(list.Volumes), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + records = make([]*types.Volume, 0) + } else { + if end >= total { + end = total + } + records = list.Volumes[start:end] + } + + for _, item := range records { + tag := make([]string, 0) + for _, val := range item.Labels { + tag = append(tag, val) + } + createTime, _ := time.Parse("2006-01-02T15:04:05Z", item.CreatedAt) + data = append(data, dto.Volume{ + CreatedAt: createTime, + Name: item.Name, + Driver: item.Driver, + Mountpoint: item.Mountpoint, + Labels: tag, + }) + } + + return int64(total), data, nil +} +func (u *ContainerService) ListVolume() ([]dto.Options, error) { + client, err := docker.NewDockerClient() + if err != nil { + return nil, err + } + list, err := client.VolumeList(context.TODO(), filters.NewArgs()) + if err != nil { + return nil, err + } + var data []dto.Options + for _, item := range list.Volumes { + data = append(data, dto.Options{ + Option: item.Name, + }) + } + return data, nil +} +func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + for _, id := range req.Ids { + if err := client.VolumeRemove(context.TODO(), id, true); err != nil { + return err + } + } + return nil +} +func (u *ContainerService) CreateVolume(req dto.VolumeCreat) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + options := volume.VolumeCreateBody{ + Name: req.Name, + Driver: req.Driver, + DriverOpts: stringsToMap(req.Options), + Labels: stringsToMap(req.Labels), + } + if _, err := client.VolumeCreate(context.TODO(), options); err != nil { + return err + } + return nil +} diff --git a/backend/app/service/entry.go b/backend/app/service/entry.go index 8ac921c16..531f94efa 100644 --- a/backend/app/service/entry.go +++ b/backend/app/service/entry.go @@ -38,7 +38,6 @@ var ( tagRepo = repo.RepoGroupApp.TagRepo appInstallRepo = repo.RepoGroupApp.AppInstallRepo appInstallResourceRepo = repo.RepoGroupApp.AppInstallResourceRpo - appContainerRepo = repo.RepoGroupApp.AppContainerRepo dataBaseRepo = repo.RepoGroupApp.DatabaseRepo appInstallBackupRepo = repo.RepoGroupApp.AppInstallBackupRepo ) diff --git a/backend/app/service/image.go b/backend/app/service/image.go index b31dd2869..5631cb2e4 100644 --- a/backend/app/service/image.go +++ b/backend/app/service/image.go @@ -11,10 +11,10 @@ import ( "os" "time" - "github.com/1Panel-dev/1Panel/app/dto" - "github.com/1Panel-dev/1Panel/constant" - "github.com/1Panel-dev/1Panel/global" - "github.com/1Panel-dev/1Panel/utils/docker" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/utils/docker" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/archive" ) diff --git a/backend/app/service/image_repo.go b/backend/app/service/image_repo.go index 72e3e160c..31c897d59 100644 --- a/backend/app/service/image_repo.go +++ b/backend/app/service/image_repo.go @@ -4,8 +4,8 @@ import ( "encoding/json" "io/ioutil" - "github.com/1Panel-dev/1Panel/app/dto" - "github.com/1Panel-dev/1Panel/constant" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" "github.com/jinzhu/copier" "github.com/pkg/errors" ) diff --git a/backend/app/service/image_test.go b/backend/app/service/image_test.go deleted file mode 100644 index 9917465d7..000000000 --- a/backend/app/service/image_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package service - -import ( - "context" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "os" - "testing" - - "github.com/1Panel-dev/1Panel/constant" - "github.com/1Panel-dev/1Panel/utils/docker" - "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/network" - "github.com/docker/docker/pkg/archive" - v1 "github.com/opencontainers/image-spec/specs-go/v1" -) - -func TestImage(t *testing.T) { - file, err := os.OpenFile(("/tmp/nginx.tar"), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666) - if err != nil { - fmt.Println(err) - } - defer file.Close() - - client, err := docker.NewDockerClient() - if err != nil { - fmt.Println(err) - } - out, err := client.ImageSave(context.TODO(), []string{"nginx:1.14.2"}) - fmt.Println(err) - defer out.Close() - if _, err = io.Copy(file, out); err != nil { - fmt.Println(err) - } -} - -func TestBuild(t *testing.T) { - client, err := docker.NewDockerClient() - if err != nil { - fmt.Println(err) - } - tar, err := archive.TarWithOptions("/tmp/testbuild/", &archive.TarOptions{}) - if err != nil { - fmt.Println(err) - } - - opts := types.ImageBuildOptions{ - Dockerfile: "Dockerfile", - Tags: []string{"hello/test:v1"}, - Remove: true, - } - res, err := client.ImageBuild(context.TODO(), tar, opts) - if err != nil { - fmt.Println(err) - } - defer res.Body.Close() -} - -func TestDeam(t *testing.T) { - file, err := ioutil.ReadFile(constant.DaemonJsonDir) - if err != nil { - fmt.Println(err) - } - deamonMap := make(map[string]interface{}) - err = json.Unmarshal(file, &deamonMap) - fmt.Println(err) - for k, v := range deamonMap { - fmt.Println(k, v) - } - if _, ok := deamonMap["insecure-registries"]; ok { - if k, v := deamonMap["insecure-registries"].(string); v { - fmt.Println("string ", k) - } - if k, v := deamonMap["insecure-registries"].([]interface{}); v { - fmt.Println("[]string ", k) - k = append(k, "172.16.10.111:8085") - deamonMap["insecure-registries"] = k - } - } - newss, err := json.Marshal(deamonMap) - if err != nil { - fmt.Println(err) - } - fmt.Println(string(newss)) - if err := ioutil.WriteFile(constant.DaemonJsonDir, newss, 0777); err != nil { - fmt.Println(err) - } -} - -func TestNetwork(t *testing.T) { - client, err := docker.NewDockerClient() - if err != nil { - fmt.Println(err) - } - options := types.ContainerListOptions{All: true} - options.Filters = filters.NewArgs() - options.Filters.Add("label", "maintainer") - ss, _ := client.ContainerList(context.TODO(), options) - fmt.Println(ss) -} - -func TestContainer(t *testing.T) { - client, err := docker.NewDockerClient() - if err != nil { - fmt.Println(err) - } - _, err = client.ContainerCreate(context.TODO(), &container.Config{}, &container.HostConfig{}, &network.NetworkingConfig{}, &v1.Platform{}, "test") - if err != nil { - fmt.Println(err) - } -} diff --git a/backend/router/ro_container.go b/backend/router/ro_container.go index bc544da90..23f487068 100644 --- a/backend/router/ro_container.go +++ b/backend/router/ro_container.go @@ -1,8 +1,8 @@ package router import ( - v1 "github.com/1Panel-dev/1Panel/app/api/v1" - "github.com/1Panel-dev/1Panel/middleware" + v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1" + "github.com/1Panel-dev/1Panel/backend/middleware" "github.com/gin-gonic/gin" ) @@ -20,6 +20,8 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) { Use(middleware.OperationRecord()) baseApi := v1.ApiGroupApp.BaseApi { + baRouter.GET("/exec", baseApi.ContainerExec) + baRouter.POST("/search", baseApi.SearchContainer) baRouter.POST("/inspect", baseApi.Inspect) baRouter.POST("", baseApi.ContainerCreate) diff --git a/backend/utils/cloud_storage/client/minio_test.go b/backend/utils/cloud_storage/client/minio_test.go index a90eae580..4ef469939 100644 --- a/backend/utils/cloud_storage/client/minio_test.go +++ b/backend/utils/cloud_storage/client/minio_test.go @@ -5,12 +5,12 @@ import ( "fmt" "testing" - "github.com/1Panel-dev/1Panel/app/model" - "github.com/1Panel-dev/1Panel/constant" - "github.com/1Panel-dev/1Panel/global" - "github.com/1Panel-dev/1Panel/init/db" - "github.com/1Panel-dev/1Panel/init/log" - "github.com/1Panel-dev/1Panel/init/viper" + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/init/db" + "github.com/1Panel-dev/1Panel/backend/init/log" + "github.com/1Panel-dev/1Panel/backend/init/viper" ) func TestMinio(t *testing.T) { diff --git a/backend/utils/cloud_storage/client/oss_test.go b/backend/utils/cloud_storage/client/oss_test.go index 675a8f71e..4f07e876e 100644 --- a/backend/utils/cloud_storage/client/oss_test.go +++ b/backend/utils/cloud_storage/client/oss_test.go @@ -7,12 +7,12 @@ import ( "os" "testing" - "github.com/1Panel-dev/1Panel/app/model" - "github.com/1Panel-dev/1Panel/constant" - "github.com/1Panel-dev/1Panel/global" - "github.com/1Panel-dev/1Panel/init/db" - "github.com/1Panel-dev/1Panel/init/log" - "github.com/1Panel-dev/1Panel/init/viper" + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/init/db" + "github.com/1Panel-dev/1Panel/backend/init/log" + "github.com/1Panel-dev/1Panel/backend/init/viper" "github.com/aliyun/aliyun-oss-go-sdk/oss" ) diff --git a/backend/utils/cloud_storage/client/sftp_test.go b/backend/utils/cloud_storage/client/sftp_test.go index 90c00273e..524c1d5ef 100644 --- a/backend/utils/cloud_storage/client/sftp_test.go +++ b/backend/utils/cloud_storage/client/sftp_test.go @@ -5,12 +5,12 @@ import ( "fmt" "testing" - "github.com/1Panel-dev/1Panel/app/model" - "github.com/1Panel-dev/1Panel/constant" - "github.com/1Panel-dev/1Panel/global" - "github.com/1Panel-dev/1Panel/init/db" - "github.com/1Panel-dev/1Panel/init/log" - "github.com/1Panel-dev/1Panel/init/viper" + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/init/db" + "github.com/1Panel-dev/1Panel/backend/init/log" + "github.com/1Panel-dev/1Panel/backend/init/viper" ) func TestCronS(t *testing.T) { diff --git a/backend/utils/terminal/exec.go b/backend/utils/terminal/exec.go new file mode 100644 index 000000000..651f84a27 --- /dev/null +++ b/backend/utils/terminal/exec.go @@ -0,0 +1,102 @@ +package terminal + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "net" + "sync" + + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/gorilla/websocket" + "github.com/pkg/errors" +) + +type ExecWsSession struct { + conn net.Conn + wsConn *websocket.Conn + + writeMutex sync.Mutex +} + +func NewExecConn(cols, rows int, wsConn *websocket.Conn, hijacked net.Conn) (*ExecWsSession, error) { + _, _ = hijacked.Write([]byte(fmt.Sprintf("stty cols %d rows %d && clear \r", cols, rows))) + + return &ExecWsSession{ + conn: hijacked, + wsConn: wsConn, + }, nil +} + +func (sws *ExecWsSession) Start(ctx context.Context, quitChan chan bool) { + go sws.handleSlaveEvent(ctx, quitChan) + go sws.receiveWsMsg(ctx, quitChan) +} + +func (sws *ExecWsSession) handleSlaveEvent(ctx context.Context, exitCh chan bool) { + defer setQuit(exitCh) + + buffer := make([]byte, 1024) + for { + n, err := sws.conn.Read(buffer) + if err != nil && errors.Is(err, net.ErrClosed) { + return + } + + if err := sws.masterWrite(buffer[:n]); err != nil { + if errors.Is(err, websocket.ErrCloseSent) { + return + } + } + } +} + +func (sws *ExecWsSession) masterWrite(data []byte) error { + sws.writeMutex.Lock() + defer sws.writeMutex.Unlock() + err := sws.wsConn.WriteMessage(websocket.TextMessage, data) + if err != nil { + return errors.Wrapf(err, "failed to write to master") + } + + return nil +} + +func (sws *ExecWsSession) receiveWsMsg(ctx context.Context, exitCh chan bool) { + wsConn := sws.wsConn + defer setQuit(exitCh) + for { + _, wsData, err := wsConn.ReadMessage() + if err != nil { + global.LOG.Errorf("reading webSocket message failed, err: %v", err) + return + } + msgObj := wsMsg{} + _ = json.Unmarshal(wsData, &msgObj) + switch msgObj.Type { + case wsMsgResize: + if msgObj.Cols > 0 && msgObj.Rows > 0 { + sws.ResizeTerminal(msgObj.Rows, msgObj.Cols) + } + case wsMsgCmd: + decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Cmd) + if err != nil { + global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err) + return + } + sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes) + case wsMsgClose: + _, _ = sws.conn.Write([]byte("exit\r")) + return + } + } +} + +func (sws *ExecWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) { + _, _ = sws.conn.Write(cmdBytes) +} + +func (sws *ExecWsSession) ResizeTerminal(width int, height int) { + _, _ = sws.conn.Write([]byte(fmt.Sprintf("stty cols %d rows %d && clear \r", width, height))) +} diff --git a/backend/utils/terminal/ws_session.go b/backend/utils/terminal/ws_session.go index cfe8d1606..50c8da4ae 100644 --- a/backend/utils/terminal/ws_session.go +++ b/backend/utils/terminal/ws_session.go @@ -37,6 +37,7 @@ func (w *safeBuffer) Reset() { const ( wsMsgCmd = "cmd" wsMsgResize = "resize" + wsMsgClose = "close" ) type wsMsg struct { diff --git a/frontend/src/api/interface/container.ts b/frontend/src/api/interface/container.ts index a5df8c930..70634a88c 100644 --- a/frontend/src/api/interface/container.ts +++ b/frontend/src/api/interface/container.ts @@ -109,6 +109,7 @@ export namespace Container { gateway: string; createdAt: string; attachable: string; + expand: boolean; } export interface NetworkCreate { name: string; @@ -169,6 +170,7 @@ export namespace Container { export interface ComposeInfo { name: string; createdAt: string; + createdBy: string; containerNumber: number; configFile: string; workdir: string; diff --git a/frontend/src/components/app-layout/menu/components/sub-item.vue b/frontend/src/components/app-layout/menu/components/sub-item.vue index 5aff37763..d2805d16a 100644 --- a/frontend/src/components/app-layout/menu/components/sub-item.vue +++ b/frontend/src/components/app-layout/menu/components/sub-item.vue @@ -9,6 +9,7 @@ + @@ -17,7 +18,11 @@ {{ $t(subItem.meta?.title as string) }} + + + + diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index fc288d354..4721cba4d 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -16,6 +16,7 @@ export default { cancel: 'Cancel', reset: 'Reset', conn: 'Connect', + disconn: 'Disconnect', clean: 'Clean', login: 'Login', close: 'Close', @@ -169,6 +170,9 @@ export default { lastHour: 'Last Hour', last10Min: 'Last 10 Minutes', + custom: 'Custom', + containerTerminal: 'Container terminal', + containerCreate: 'Container create', port: 'Port', exposePort: 'Expose port', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index f1e7d7793..e50074c17 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -17,6 +17,7 @@ export default { cancel: '取消', reset: '重置', conn: '连接', + disconn: '断开', clean: '清空', login: '登录', close: '关闭', @@ -167,6 +168,9 @@ export default { lastHour: '最近 1 小时', last10Min: '最近 10 分钟', + custom: '自定义', + containerTerminal: '容器终端', + containerCreate: '容器创建', port: '端口', exposePort: '暴露端口', diff --git a/frontend/src/routers/modules/container.ts b/frontend/src/routers/modules/container.ts index 01d7108cd..b7554b758 100644 --- a/frontend/src/routers/modules/container.ts +++ b/frontend/src/routers/modules/container.ts @@ -24,7 +24,6 @@ const containerRouter = { path: 'image', name: 'Image', component: () => import('@/views/container/image/index.vue'), - props: true, hidden: true, meta: { activeMenu: '/containers', @@ -34,7 +33,6 @@ const containerRouter = { path: 'network', name: 'Network', component: () => import('@/views/container/network/index.vue'), - props: true, hidden: true, meta: { activeMenu: '/containers', @@ -44,7 +42,6 @@ const containerRouter = { path: 'volume', name: 'Volume', component: () => import('@/views/container/volume/index.vue'), - props: true, hidden: true, meta: { activeMenu: '/containers', @@ -54,7 +51,6 @@ const containerRouter = { path: 'repo', name: 'Repo', component: () => import('@/views/container/repo/index.vue'), - props: true, hidden: true, meta: { activeMenu: '/containers', @@ -64,7 +60,6 @@ const containerRouter = { path: 'compose', name: 'Compose', component: () => import('@/views/container/compose/index.vue'), - props: true, hidden: true, meta: { activeMenu: '/containers', @@ -74,7 +69,6 @@ const containerRouter = { path: 'template', name: 'composeTemplate', component: () => import('@/views/container/template/index.vue'), - props: true, hidden: true, meta: { activeMenu: '/containers', diff --git a/frontend/src/views/container/compose/index.vue b/frontend/src/views/container/compose/index.vue index 7b189c432..35dfb65e2 100644 --- a/frontend/src/views/container/compose/index.vue +++ b/frontend/src/views/container/compose/index.vue @@ -40,15 +40,16 @@ {{ row.name }} + - + @@ -166,6 +167,7 @@ import ComplexTable from '@/components/complex-table/index.vue'; import CreateDialog from '@/views/container/container/create/index.vue'; import MonitorDialog from '@/views/container/container/monitor/index.vue'; +import TerminalDialog from '@/views/container/container/terminal/index.vue'; import Submenu from '@/views/container/index.vue'; import { Codemirror } from 'vue-codemirror'; import { javascript } from '@codemirror/lang-javascript'; @@ -259,6 +261,11 @@ const onMonitor = (containerID: string) => { dialogMonitorRef.value!.acceptParams({ containerID: containerID }); }; +const dialogTerminalRef = ref(); +const onTerminal = (containerID: string) => { + dialogTerminalRef.value!.acceptParams({ containerID: containerID }); +}; + const onInspect = async (id: string) => { const res = await inspect({ id: id, type: 'container' }); detailInfo.value = JSON.stringify(JSON.parse(res.data), null, 2); @@ -375,8 +382,20 @@ const onOperate = async (operation: string) => { }; const buttons = [ + { + label: i18n.global.t('file.terminal'), + disabled: (row: Container.ContainerInfo) => { + return row.state !== 'running'; + }, + click: (row: Container.ContainerInfo) => { + onTerminal(row.containerID); + }, + }, { label: i18n.global.t('container.monitor'), + disabled: (row: Container.ContainerInfo) => { + return row.state !== 'running'; + }, click: (row: Container.ContainerInfo) => { onMonitor(row.containerID); }, diff --git a/frontend/src/views/container/container/terminal/index.vue b/frontend/src/views/container/container/terminal/index.vue new file mode 100644 index 000000000..91a8a94da --- /dev/null +++ b/frontend/src/views/container/container/terminal/index.vue @@ -0,0 +1,207 @@ + + + diff --git a/frontend/src/views/container/index.vue b/frontend/src/views/container/index.vue index 07be67a67..4a20d7b83 100644 --- a/frontend/src/views/container/index.vue +++ b/frontend/src/views/container/index.vue @@ -54,7 +54,7 @@ const props = withDefaults(defineProps(), { activeName: 'container', }); -const active = ref(); +const active = ref('container'); onMounted(() => { if (props.activeName) { diff --git a/frontend/src/views/container/network/index.vue b/frontend/src/views/container/network/index.vue index 685717d79..b921e354f 100644 --- a/frontend/src/views/container/network/index.vue +++ b/frontend/src/views/container/network/index.vue @@ -28,10 +28,15 @@ diff --git a/go.mod b/go.mod index a9649206d..ba497606c 100644 --- a/go.mod +++ b/go.mod @@ -148,5 +148,4 @@ require ( golang.org/x/tools v0.1.12 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.66.6 // indirect - gotest.tools/v3 v3.3.0 // indirect ) diff --git a/go.sum b/go.sum index 9735ee707..225e98255 100644 --- a/go.sum +++ b/go.sum @@ -126,12 +126,9 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -<<<<<<< HEAD:go.sum -======= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= ->>>>>>> 94a2b8a (feat: 实现容器网络功能):backend/go.sum github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -147,12 +144,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -<<<<<<< HEAD:go.sum -github.com/compose-spec/compose-go v1.6.0 h1:7Ol/UULMUtbPmB0EYrETASRoum821JpOh/XaEf+hN+Q= -github.com/compose-spec/compose-go v1.6.0/go.mod h1:os+Ulh2jlZxY1XT1hbciERadjSUU/BtZ6+gcN7vD7J0= -======= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/compose-spec/compose-go v1.6.0 h1:7Ol/UULMUtbPmB0EYrETASRoum821JpOh/XaEf+hN+Q= +github.com/compose-spec/compose-go v1.6.0/go.mod h1:os+Ulh2jlZxY1XT1hbciERadjSUU/BtZ6+gcN7vD7J0= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= @@ -245,7 +240,6 @@ github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/ github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= ->>>>>>> 94a2b8a (feat: 实现容器网络功能):backend/go.sum github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= @@ -286,17 +280,14 @@ github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -<<<<<<< HEAD:go.sum +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb h1:oCCuuU3kMO3sjZH/p7LamvQNW9SWoT4yQuMGcdSxGAE= github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb/go.mod h1:28YO/VJk9/64+sTGNuYaBjWxrXTPrj0C0XmgTIOjxX4= -======= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= ->>>>>>> 94a2b8a (feat: 实现容器网络功能):backend/go.sum github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= @@ -556,16 +547,13 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -<<<<<<< HEAD:go.sum -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -======= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= ->>>>>>> 94a2b8a (feat: 实现容器网络功能):backend/go.sum +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -665,13 +653,10 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -<<<<<<< HEAD:go.sum -github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -======= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= ->>>>>>> 94a2b8a (feat: 实现容器网络功能):backend/go.sum +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -952,12 +937,6 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= @@ -966,9 +945,13 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17 github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f h1:C8De+7emQKojPBC+mXA0fr39XN5mKjRm9IUzdxI4whI= github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= @@ -1487,7 +1470,6 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= -gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=