mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-24 02:59:16 +08:00
feat: 完成容器终端功能
This commit is contained in:
parent
3ca1e97469
commit
e58e5cef0d
@ -1,10 +1,10 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||||
"github.com/1Panel-dev/1Panel/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||||
"github.com/1Panel-dev/1Panel/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"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/gin-gonic/gin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *BaseApi) SearchContainer(c *gin.Context) {
|
func (b *BaseApi) SearchContainer(c *gin.Context) {
|
||||||
@ -158,6 +163,59 @@ func (b *BaseApi) Inspect(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, result)
|
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) {
|
func (b *BaseApi) ContainerLogs(c *gin.Context) {
|
||||||
var req dto.ContainerLog
|
var req dto.ContainerLog
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||||
"github.com/1Panel-dev/1Panel/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||||
"github.com/1Panel-dev/1Panel/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -112,6 +112,7 @@ type BatchDelete struct {
|
|||||||
type ComposeInfo struct {
|
type ComposeInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
CreatedAt string `json:"createdAt"`
|
CreatedAt string `json:"createdAt"`
|
||||||
|
CreatedBy string `json:"createdBy"`
|
||||||
ContainerNumber int `json:"containerNumber"`
|
ContainerNumber int `json:"containerNumber"`
|
||||||
ConfigFile string `json:"configFile"`
|
ConfigFile string `json:"configFile"`
|
||||||
Workdir string `json:"workdir"`
|
Workdir string `json:"workdir"`
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/1Panel-dev/1Panel/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ComposeTemplateRepo struct{}
|
type ComposeTemplateRepo struct{}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/1Panel-dev/1Panel/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ImageRepoRepo struct{}
|
type ImageRepoRepo struct{}
|
||||||
|
@ -4,6 +4,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"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/dto"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
@ -15,15 +25,6 @@ import (
|
|||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DatabaseOp string
|
type DatabaseOp string
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/1Panel-dev/1Panel/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -1,28 +1,23 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||||
"github.com/1Panel-dev/1Panel/utils/docker"
|
|
||||||
"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/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
@ -30,10 +25,6 @@ import (
|
|||||||
|
|
||||||
type ContainerService struct{}
|
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 {
|
type IContainerService interface {
|
||||||
Page(req dto.PageContainer) (int64, interface{}, error)
|
Page(req dto.PageContainer) (int64, interface{}, error)
|
||||||
PageNetwork(req dto.PageInfo) (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
|
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) {
|
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -387,187 +256,6 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro
|
|||||||
return &data, nil
|
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 {
|
func stringsToMap(list []string) map[string]string {
|
||||||
var lableMap = make(map[string]string)
|
var lableMap = make(map[string]string)
|
||||||
for _, label := range list {
|
for _, label := range list {
|
||||||
|
149
backend/app/service/container_compose.go
Normal file
149
backend/app/service/container_compose.go
Normal file
@ -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
|
||||||
|
}
|
106
backend/app/service/container_network.go
Normal file
106
backend/app/service/container_network.go
Normal file
@ -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
|
||||||
|
}
|
98
backend/app/service/container_volume.go
Normal file
98
backend/app/service/container_volume.go
Normal file
@ -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
|
||||||
|
}
|
@ -38,7 +38,6 @@ var (
|
|||||||
tagRepo = repo.RepoGroupApp.TagRepo
|
tagRepo = repo.RepoGroupApp.TagRepo
|
||||||
appInstallRepo = repo.RepoGroupApp.AppInstallRepo
|
appInstallRepo = repo.RepoGroupApp.AppInstallRepo
|
||||||
appInstallResourceRepo = repo.RepoGroupApp.AppInstallResourceRpo
|
appInstallResourceRepo = repo.RepoGroupApp.AppInstallResourceRpo
|
||||||
appContainerRepo = repo.RepoGroupApp.AppContainerRepo
|
|
||||||
dataBaseRepo = repo.RepoGroupApp.DatabaseRepo
|
dataBaseRepo = repo.RepoGroupApp.DatabaseRepo
|
||||||
appInstallBackupRepo = repo.RepoGroupApp.AppInstallBackupRepo
|
appInstallBackupRepo = repo.RepoGroupApp.AppInstallBackupRepo
|
||||||
)
|
)
|
||||||
|
@ -11,10 +11,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/1Panel-dev/1Panel/utils/docker"
|
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
)
|
)
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
v1 "github.com/1Panel-dev/1Panel/app/api/v1"
|
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||||
"github.com/1Panel-dev/1Panel/middleware"
|
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,6 +20,8 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
|||||||
Use(middleware.OperationRecord())
|
Use(middleware.OperationRecord())
|
||||||
baseApi := v1.ApiGroupApp.BaseApi
|
baseApi := v1.ApiGroupApp.BaseApi
|
||||||
{
|
{
|
||||||
|
baRouter.GET("/exec", baseApi.ContainerExec)
|
||||||
|
|
||||||
baRouter.POST("/search", baseApi.SearchContainer)
|
baRouter.POST("/search", baseApi.SearchContainer)
|
||||||
baRouter.POST("/inspect", baseApi.Inspect)
|
baRouter.POST("/inspect", baseApi.Inspect)
|
||||||
baRouter.POST("", baseApi.ContainerCreate)
|
baRouter.POST("", baseApi.ContainerCreate)
|
||||||
|
@ -5,12 +5,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/1Panel-dev/1Panel/init/db"
|
"github.com/1Panel-dev/1Panel/backend/init/db"
|
||||||
"github.com/1Panel-dev/1Panel/init/log"
|
"github.com/1Panel-dev/1Panel/backend/init/log"
|
||||||
"github.com/1Panel-dev/1Panel/init/viper"
|
"github.com/1Panel-dev/1Panel/backend/init/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMinio(t *testing.T) {
|
func TestMinio(t *testing.T) {
|
||||||
|
@ -7,12 +7,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/1Panel-dev/1Panel/init/db"
|
"github.com/1Panel-dev/1Panel/backend/init/db"
|
||||||
"github.com/1Panel-dev/1Panel/init/log"
|
"github.com/1Panel-dev/1Panel/backend/init/log"
|
||||||
"github.com/1Panel-dev/1Panel/init/viper"
|
"github.com/1Panel-dev/1Panel/backend/init/viper"
|
||||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,12 +5,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/1Panel-dev/1Panel/init/db"
|
"github.com/1Panel-dev/1Panel/backend/init/db"
|
||||||
"github.com/1Panel-dev/1Panel/init/log"
|
"github.com/1Panel-dev/1Panel/backend/init/log"
|
||||||
"github.com/1Panel-dev/1Panel/init/viper"
|
"github.com/1Panel-dev/1Panel/backend/init/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCronS(t *testing.T) {
|
func TestCronS(t *testing.T) {
|
||||||
|
102
backend/utils/terminal/exec.go
Normal file
102
backend/utils/terminal/exec.go
Normal file
@ -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)))
|
||||||
|
}
|
@ -37,6 +37,7 @@ func (w *safeBuffer) Reset() {
|
|||||||
const (
|
const (
|
||||||
wsMsgCmd = "cmd"
|
wsMsgCmd = "cmd"
|
||||||
wsMsgResize = "resize"
|
wsMsgResize = "resize"
|
||||||
|
wsMsgClose = "close"
|
||||||
)
|
)
|
||||||
|
|
||||||
type wsMsg struct {
|
type wsMsg struct {
|
||||||
|
@ -109,6 +109,7 @@ export namespace Container {
|
|||||||
gateway: string;
|
gateway: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
attachable: string;
|
attachable: string;
|
||||||
|
expand: boolean;
|
||||||
}
|
}
|
||||||
export interface NetworkCreate {
|
export interface NetworkCreate {
|
||||||
name: string;
|
name: string;
|
||||||
@ -169,6 +170,7 @@ export namespace Container {
|
|||||||
export interface ComposeInfo {
|
export interface ComposeInfo {
|
||||||
name: string;
|
name: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
createdBy: string;
|
||||||
containerNumber: number;
|
containerNumber: number;
|
||||||
configFile: string;
|
configFile: string;
|
||||||
workdir: string;
|
workdir: string;
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<SubItem :menuList="subItem.children" />
|
<SubItem :menuList="subItem.children" />
|
||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
|
|
||||||
<el-menu-item v-else-if="subItem.children && subItem.children.length === 1" :index="subItem.children[0].path">
|
<el-menu-item v-else-if="subItem.children && subItem.children.length === 1" :index="subItem.children[0].path">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<SvgIcon :iconName="(subItem.meta?.icon as string)" :className="'svg-icon'"></SvgIcon>
|
<SvgIcon :iconName="(subItem.meta?.icon as string)" :className="'svg-icon'"></SvgIcon>
|
||||||
@ -17,7 +18,11 @@
|
|||||||
<span>{{ $t(subItem.meta?.title as string) }}</span>
|
<span>{{ $t(subItem.meta?.title as string) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
|
||||||
<el-menu-item v-else :index="subItem.path">
|
<el-menu-item v-else :index="subItem.path">
|
||||||
|
<el-icon v-if="subItem.meta?.icon">
|
||||||
|
<SvgIcon :iconName="(subItem.meta?.icon as string)" :className="'svg-icon'"></SvgIcon>
|
||||||
|
</el-icon>
|
||||||
<template #title>
|
<template #title>
|
||||||
<span style="margin-left: 10px">{{ $t(subItem.meta?.title as string) }}</span>
|
<span style="margin-left: 10px">{{ $t(subItem.meta?.title as string) }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -16,6 +16,7 @@ export default {
|
|||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
conn: 'Connect',
|
conn: 'Connect',
|
||||||
|
disconn: 'Disconnect',
|
||||||
clean: 'Clean',
|
clean: 'Clean',
|
||||||
login: 'Login',
|
login: 'Login',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
@ -169,6 +170,9 @@ export default {
|
|||||||
lastHour: 'Last Hour',
|
lastHour: 'Last Hour',
|
||||||
last10Min: 'Last 10 Minutes',
|
last10Min: 'Last 10 Minutes',
|
||||||
|
|
||||||
|
custom: 'Custom',
|
||||||
|
containerTerminal: 'Container terminal',
|
||||||
|
|
||||||
containerCreate: 'Container create',
|
containerCreate: 'Container create',
|
||||||
port: 'Port',
|
port: 'Port',
|
||||||
exposePort: 'Expose port',
|
exposePort: 'Expose port',
|
||||||
|
@ -17,6 +17,7 @@ export default {
|
|||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
conn: '连接',
|
conn: '连接',
|
||||||
|
disconn: '断开',
|
||||||
clean: '清空',
|
clean: '清空',
|
||||||
login: '登录',
|
login: '登录',
|
||||||
close: '关闭',
|
close: '关闭',
|
||||||
@ -167,6 +168,9 @@ export default {
|
|||||||
lastHour: '最近 1 小时',
|
lastHour: '最近 1 小时',
|
||||||
last10Min: '最近 10 分钟',
|
last10Min: '最近 10 分钟',
|
||||||
|
|
||||||
|
custom: '自定义',
|
||||||
|
containerTerminal: '容器终端',
|
||||||
|
|
||||||
containerCreate: '容器创建',
|
containerCreate: '容器创建',
|
||||||
port: '端口',
|
port: '端口',
|
||||||
exposePort: '暴露端口',
|
exposePort: '暴露端口',
|
||||||
|
@ -24,7 +24,6 @@ const containerRouter = {
|
|||||||
path: 'image',
|
path: 'image',
|
||||||
name: 'Image',
|
name: 'Image',
|
||||||
component: () => import('@/views/container/image/index.vue'),
|
component: () => import('@/views/container/image/index.vue'),
|
||||||
props: true,
|
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
activeMenu: '/containers',
|
activeMenu: '/containers',
|
||||||
@ -34,7 +33,6 @@ const containerRouter = {
|
|||||||
path: 'network',
|
path: 'network',
|
||||||
name: 'Network',
|
name: 'Network',
|
||||||
component: () => import('@/views/container/network/index.vue'),
|
component: () => import('@/views/container/network/index.vue'),
|
||||||
props: true,
|
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
activeMenu: '/containers',
|
activeMenu: '/containers',
|
||||||
@ -44,7 +42,6 @@ const containerRouter = {
|
|||||||
path: 'volume',
|
path: 'volume',
|
||||||
name: 'Volume',
|
name: 'Volume',
|
||||||
component: () => import('@/views/container/volume/index.vue'),
|
component: () => import('@/views/container/volume/index.vue'),
|
||||||
props: true,
|
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
activeMenu: '/containers',
|
activeMenu: '/containers',
|
||||||
@ -54,7 +51,6 @@ const containerRouter = {
|
|||||||
path: 'repo',
|
path: 'repo',
|
||||||
name: 'Repo',
|
name: 'Repo',
|
||||||
component: () => import('@/views/container/repo/index.vue'),
|
component: () => import('@/views/container/repo/index.vue'),
|
||||||
props: true,
|
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
activeMenu: '/containers',
|
activeMenu: '/containers',
|
||||||
@ -64,7 +60,6 @@ const containerRouter = {
|
|||||||
path: 'compose',
|
path: 'compose',
|
||||||
name: 'Compose',
|
name: 'Compose',
|
||||||
component: () => import('@/views/container/compose/index.vue'),
|
component: () => import('@/views/container/compose/index.vue'),
|
||||||
props: true,
|
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
activeMenu: '/containers',
|
activeMenu: '/containers',
|
||||||
@ -74,7 +69,6 @@ const containerRouter = {
|
|||||||
path: 'template',
|
path: 'template',
|
||||||
name: 'composeTemplate',
|
name: 'composeTemplate',
|
||||||
component: () => import('@/views/container/template/index.vue'),
|
component: () => import('@/views/container/template/index.vue'),
|
||||||
props: true,
|
|
||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {
|
meta: {
|
||||||
activeMenu: '/containers',
|
activeMenu: '/containers',
|
||||||
|
@ -40,15 +40,16 @@
|
|||||||
<el-link @click="goContainer(row.name)" type="primary">{{ row.name }}</el-link>
|
<el-link @click="goContainer(row.name)" type="primary">{{ row.name }}</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('container.from')" prop="createdBy" min-width="80" fix />
|
||||||
<el-table-column :label="$t('container.containerNumber')" prop="containerNumber" min-width="80" fix />
|
<el-table-column :label="$t('container.containerNumber')" prop="containerNumber" min-width="80" fix />
|
||||||
<el-table-column :label="$t('container.containerNumber')" prop="contaienrs" min-width="80" fix>
|
<el-table-column :label="$t('container.container')" prop="contaienrs" min-width="80" fix>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div v-for="(item, index) in row.containers" :key="index">
|
<div v-for="(item, index) in row.containers" :key="index">
|
||||||
<div v-if="row.expand || (!row.expand && index < 1)">
|
<div v-if="row.expand || (!row.expand && index < 3)">
|
||||||
<el-tag>{{ item.name }} [{{ item.state }}]</el-tag>
|
<el-tag>{{ item.name }} [{{ item.state }}]</el-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!row.expand">
|
<div v-if="!row.expand && row.containers.length > 3">
|
||||||
<el-button type="primary" link @click="row.expand = true">
|
<el-button type="primary" link @click="row.expand = true">
|
||||||
{{ $t('commons.button.expand') }}...
|
{{ $t('commons.button.expand') }}...
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -91,6 +92,7 @@ const search = async () => {
|
|||||||
await searchCompose(params).then((res) => {
|
await searchCompose(params).then((res) => {
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
data.value = res.data.items;
|
data.value = res.data.items;
|
||||||
|
paginationConfig.total = res.data.total;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -132,7 +134,6 @@ const onOperate = async (operation: string) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// const buttons = [];
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
search();
|
search();
|
||||||
|
@ -96,6 +96,9 @@ const rules = reactive({
|
|||||||
const loadTemplates = async () => {
|
const loadTemplates = async () => {
|
||||||
const res = await listComposeTemplate();
|
const res = await listComposeTemplate();
|
||||||
templateOptions.value = res.data;
|
templateOptions.value = res.data;
|
||||||
|
if (templateOptions.value && templateOptions.value.length !== 0) {
|
||||||
|
form.template = templateOptions.value[0].id;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const acceptParams = (): void => {
|
const acceptParams = (): void => {
|
||||||
@ -104,7 +107,6 @@ const acceptParams = (): void => {
|
|||||||
form.from = 'edit';
|
form.from = 'edit';
|
||||||
form.path = '';
|
form.path = '';
|
||||||
form.file = '';
|
form.file = '';
|
||||||
form.template = 0;
|
|
||||||
loadTemplates();
|
loadTemplates();
|
||||||
};
|
};
|
||||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
@ -159,6 +159,7 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
<CreateDialog @search="search" ref="dialogCreateRef" />
|
<CreateDialog @search="search" ref="dialogCreateRef" />
|
||||||
<MonitorDialog ref="dialogMonitorRef" />
|
<MonitorDialog ref="dialogMonitorRef" />
|
||||||
|
<TerminalDialog ref="dialogTerminalRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -166,6 +167,7 @@
|
|||||||
import ComplexTable from '@/components/complex-table/index.vue';
|
import ComplexTable from '@/components/complex-table/index.vue';
|
||||||
import CreateDialog from '@/views/container/container/create/index.vue';
|
import CreateDialog from '@/views/container/container/create/index.vue';
|
||||||
import MonitorDialog from '@/views/container/container/monitor/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 Submenu from '@/views/container/index.vue';
|
||||||
import { Codemirror } from 'vue-codemirror';
|
import { Codemirror } from 'vue-codemirror';
|
||||||
import { javascript } from '@codemirror/lang-javascript';
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
@ -259,6 +261,11 @@ const onMonitor = (containerID: string) => {
|
|||||||
dialogMonitorRef.value!.acceptParams({ containerID: containerID });
|
dialogMonitorRef.value!.acceptParams({ containerID: containerID });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dialogTerminalRef = ref();
|
||||||
|
const onTerminal = (containerID: string) => {
|
||||||
|
dialogTerminalRef.value!.acceptParams({ containerID: containerID });
|
||||||
|
};
|
||||||
|
|
||||||
const onInspect = async (id: string) => {
|
const onInspect = async (id: string) => {
|
||||||
const res = await inspect({ id: id, type: 'container' });
|
const res = await inspect({ id: id, type: 'container' });
|
||||||
detailInfo.value = JSON.stringify(JSON.parse(res.data), null, 2);
|
detailInfo.value = JSON.stringify(JSON.parse(res.data), null, 2);
|
||||||
@ -375,8 +382,20 @@ const onOperate = async (operation: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const buttons = [
|
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'),
|
label: i18n.global.t('container.monitor'),
|
||||||
|
disabled: (row: Container.ContainerInfo) => {
|
||||||
|
return row.state !== 'running';
|
||||||
|
},
|
||||||
click: (row: Container.ContainerInfo) => {
|
click: (row: Container.ContainerInfo) => {
|
||||||
onMonitor(row.containerID);
|
onMonitor(row.containerID);
|
||||||
},
|
},
|
||||||
|
207
frontend/src/views/container/container/terminal/index.vue
Normal file
207
frontend/src/views/container/container/terminal/index.vue
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="terminalVisiable"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
@close="onClose"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
width="70%"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>{{ $t('container.containerTerminal') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" :model="form" label-width="80px">
|
||||||
|
<el-form-item label="User" prop="user" :rules="Rules.requiredInput">
|
||||||
|
<el-input style="width: 30%" clearable placeholder="root" v-model="form.user" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.custom')" prop="custom">
|
||||||
|
<el-switch v-model="form.isCustom" @change="form.command = ''" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="form.isCustom" label="Command" prop="command" :rules="Rules.requiredInput">
|
||||||
|
<el-input style="width: 30%" clearable v-model="form.command" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="!form.isCustom" label="Command" prop="command" :rules="Rules.requiredSelect">
|
||||||
|
<el-select style="width: 30%" allow-create filterable clearable v-model="form.command">
|
||||||
|
<el-option value="/bin/ash" label="/bin/ash" />
|
||||||
|
<el-option value="/bin/bash" label="/bin/bash" />
|
||||||
|
<el-option value="/bin/sh" label="/bin/sh" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button v-if="!terminalOpen" @click="initTerm(formRef)">{{ $t('commons.button.conn') }}</el-button>
|
||||||
|
<el-button v-else @click="onClose()">{{ $t('commons.button.disconn') }}</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div :id="'terminal-exec'"></div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="terminalVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { ElForm, FormInstance } from 'element-plus';
|
||||||
|
import { Terminal } from 'xterm';
|
||||||
|
import { AttachAddon } from 'xterm-addon-attach';
|
||||||
|
import { Base64 } from 'js-base64';
|
||||||
|
import 'xterm/css/xterm.css';
|
||||||
|
import { FitAddon } from 'xterm-addon-fit';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
|
||||||
|
const terminalVisiable = ref(false);
|
||||||
|
const terminalOpen = ref(false);
|
||||||
|
const fitAddon = new FitAddon();
|
||||||
|
let terminalSocket = ref(null) as unknown as WebSocket;
|
||||||
|
let term = ref(null) as unknown as Terminal;
|
||||||
|
const loading = ref(true);
|
||||||
|
const runRealTerminal = () => {
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
const form = reactive({
|
||||||
|
isCustom: false,
|
||||||
|
command: '',
|
||||||
|
user: '',
|
||||||
|
containerID: '',
|
||||||
|
});
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
containerID: string;
|
||||||
|
}
|
||||||
|
const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||||
|
terminalVisiable.value = true;
|
||||||
|
form.containerID = params.containerID;
|
||||||
|
form.isCustom = false;
|
||||||
|
form.user = 'root';
|
||||||
|
form.command = '/bin/bash';
|
||||||
|
terminalOpen.value = false;
|
||||||
|
window.addEventListener('resize', changeTerminalSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onWSReceive = (message: any) => {
|
||||||
|
if (!isJson(message.data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = JSON.parse(message.data);
|
||||||
|
term.element && term.focus();
|
||||||
|
term.write(data.Data);
|
||||||
|
};
|
||||||
|
|
||||||
|
function isJson(str: string) {
|
||||||
|
try {
|
||||||
|
if (typeof JSON.parse(str) === 'object') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorRealTerminal = (ex: any) => {
|
||||||
|
let message = ex.message;
|
||||||
|
if (!message) message = 'disconnected';
|
||||||
|
term.write(`\x1b[31m${message}\x1b[m\r\n`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeRealTerminal = (ev: CloseEvent) => {
|
||||||
|
term.write(ev.reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initTerm = (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
terminalOpen.value = true;
|
||||||
|
let ifm = document.getElementById('terminal-exec') as HTMLInputElement | null;
|
||||||
|
term = new Terminal({
|
||||||
|
lineHeight: 1.2,
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
|
||||||
|
theme: {
|
||||||
|
background: '#000000',
|
||||||
|
},
|
||||||
|
cursorBlink: true,
|
||||||
|
cursorStyle: 'underline',
|
||||||
|
scrollback: 100,
|
||||||
|
tabStopWidth: 4,
|
||||||
|
});
|
||||||
|
if (ifm) {
|
||||||
|
term.open(ifm);
|
||||||
|
terminalSocket = new WebSocket(
|
||||||
|
`ws://localhost:9999/api/v1/containers/exec?containerid=${form.containerID}&cols=${term.cols}&rows=${term.rows}&user=${form.user}&command=${form.command}`,
|
||||||
|
);
|
||||||
|
terminalSocket.onopen = runRealTerminal;
|
||||||
|
terminalSocket.onmessage = onWSReceive;
|
||||||
|
terminalSocket.onclose = closeRealTerminal;
|
||||||
|
terminalSocket.onerror = errorRealTerminal;
|
||||||
|
term.onData((data: any) => {
|
||||||
|
if (isWsOpen()) {
|
||||||
|
terminalSocket.send(
|
||||||
|
JSON.stringify({
|
||||||
|
type: 'cmd',
|
||||||
|
cmd: Base64.encode(data),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
term.loadAddon(new AttachAddon(terminalSocket));
|
||||||
|
term.loadAddon(fitAddon);
|
||||||
|
setTimeout(() => {
|
||||||
|
fitAddon.fit();
|
||||||
|
if (isWsOpen()) {
|
||||||
|
terminalSocket.send(
|
||||||
|
JSON.stringify({
|
||||||
|
type: 'resize',
|
||||||
|
cols: term.cols,
|
||||||
|
rows: term.rows,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 30);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fitTerm = () => {
|
||||||
|
fitAddon.fit();
|
||||||
|
};
|
||||||
|
|
||||||
|
const isWsOpen = () => {
|
||||||
|
const readyState = terminalSocket && terminalSocket.readyState;
|
||||||
|
if (readyState) {
|
||||||
|
return readyState === 1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
terminalOpen.value = false;
|
||||||
|
window.removeEventListener('resize', changeTerminalSize);
|
||||||
|
if (isWsOpen()) {
|
||||||
|
terminalSocket && terminalSocket.close();
|
||||||
|
term.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeTerminalSize() {
|
||||||
|
fitTerm();
|
||||||
|
const { cols, rows } = term;
|
||||||
|
if (isWsOpen()) {
|
||||||
|
terminalSocket.send(
|
||||||
|
JSON.stringify({
|
||||||
|
type: 'resize',
|
||||||
|
cols: cols,
|
||||||
|
rows: rows,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
@ -54,7 +54,7 @@ const props = withDefaults(defineProps<MenuProps>(), {
|
|||||||
activeName: 'container',
|
activeName: 'container',
|
||||||
});
|
});
|
||||||
|
|
||||||
const active = ref();
|
const active = ref('container');
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.activeName) {
|
if (props.activeName) {
|
||||||
|
@ -28,10 +28,15 @@
|
|||||||
<el-table-column :label="$t('container.gateway')" min-width="80" prop="gateway" fix />
|
<el-table-column :label="$t('container.gateway')" min-width="80" prop="gateway" fix />
|
||||||
<el-table-column :label="$t('container.tag')" min-width="140" fix>
|
<el-table-column :label="$t('container.tag')" min-width="140" fix>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div v-for="(item, index) of row.labels" :key="index">
|
<div v-for="(item, index) in row.labels" :key="index">
|
||||||
<el-tooltip class="item" :content="item" placement="top">
|
<div v-if="row.expand || (!row.expand && index < 3)">
|
||||||
<el-tag>{{ item }}</el-tag>
|
<el-tag>{{ item }}</el-tag>
|
||||||
</el-tooltip>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!row.expand && row.labels.length > 3">
|
||||||
|
<el-button type="primary" link @click="row.expand = true">
|
||||||
|
{{ $t('commons.button.expand') }}...
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
1
go.mod
1
go.mod
@ -148,5 +148,4 @@ require (
|
|||||||
golang.org/x/tools v0.1.12 // indirect
|
golang.org/x/tools v0.1.12 // indirect
|
||||||
google.golang.org/protobuf v1.28.0 // indirect
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.66.6 // indirect
|
gopkg.in/ini.v1 v1.66.6 // indirect
|
||||||
gotest.tools/v3 v3.3.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
40
go.sum
40
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.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
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/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.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
||||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
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/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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
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-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-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/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/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/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-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-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
|
||||||
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
|
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.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4=
|
||||||
github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=
|
github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
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/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-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||||
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
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/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 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
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 h1:oCCuuU3kMO3sjZH/p7LamvQNW9SWoT4yQuMGcdSxGAE=
|
||||||
github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb/go.mod h1:28YO/VJk9/64+sTGNuYaBjWxrXTPrj0C0XmgTIOjxX4=
|
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/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/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 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-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=
|
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 h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
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=
|
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/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-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/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.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
github.com/imdario/mergo v0.3.8/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.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.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/imdario/mergo v0.3.12/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/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/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=
|
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.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 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
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.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
github.com/mattn/go-shellwords v1.0.6/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 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 h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
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.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/urfave/cli v1.22.2/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/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 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.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
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/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-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
|
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/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/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 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/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 h1:C8De+7emQKojPBC+mXA0fr39XN5mKjRm9IUzdxI4whI=
|
||||||
github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
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.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
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 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-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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
Loading…
Reference in New Issue
Block a user