mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-27 20:49:03 +08:00
feat: 实现容器网络功能
This commit is contained in:
parent
f98f9a5872
commit
1d2a00cc6e
@ -80,3 +80,115 @@ func (b *BaseApi) ContainerLogs(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
helper.SuccessWithData(c, logs)
|
helper.SuccessWithData(c, logs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) SearchNetwork(c *gin.Context) {
|
||||||
|
var req dto.PageInfo
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
total, list, err := containerService.PageNetwork(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, dto.PageResult{
|
||||||
|
Items: list,
|
||||||
|
Total: total,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func (b *BaseApi) DeleteNetwork(c *gin.Context) {
|
||||||
|
var req dto.BatchDelete
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := containerService.DeleteNetwork(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
func (b *BaseApi) CreateNetwork(c *gin.Context) {
|
||||||
|
var req dto.NetworkCreat
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := containerService.CreateNetwork(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) SearchVolume(c *gin.Context) {
|
||||||
|
var req dto.PageInfo
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
total, list, err := containerService.PageVolume(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, dto.PageResult{
|
||||||
|
Items: list,
|
||||||
|
Total: total,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func (b *BaseApi) DeleteVolume(c *gin.Context) {
|
||||||
|
var req dto.BatchDelete
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := containerService.DeleteVolume(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
func (b *BaseApi) CreateVolume(c *gin.Context) {
|
||||||
|
var req dto.VolumeCreat
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := containerService.CreateVolume(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
@ -31,6 +31,25 @@ func (b *BaseApi) SearchImage(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) ImageBuild(c *gin.Context) {
|
||||||
|
var req dto.ImageBuild
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := imageService.ImageBuild(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BaseApi) ImagePull(c *gin.Context) {
|
func (b *BaseApi) ImagePull(c *gin.Context) {
|
||||||
var req dto.ImagePull
|
var req dto.ImagePull
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
type PageContainer struct {
|
type PageContainer struct {
|
||||||
PageInfo
|
PageInfo
|
||||||
Status string `json:"status" validate:"required,oneof=all running"`
|
Status string `json:"status" validate:"required,oneof=all running"`
|
||||||
@ -25,3 +27,44 @@ type ContainerOperation struct {
|
|||||||
Operation string `json:"operation" validate:"required,oneof=start stop reStart kill pause unPause reName remove"`
|
Operation string `json:"operation" validate:"required,oneof=start stop reStart kill pause unPause reName remove"`
|
||||||
NewName string `json:"newName"`
|
NewName string `json:"newName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Network struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Labels []string `json:"labels"`
|
||||||
|
Driver string `json:"driver"`
|
||||||
|
IPAMDriver string `json:"ipamDriver"`
|
||||||
|
IPV4Subnet string `json:"ipv4Subnet"`
|
||||||
|
IPV4Gateway string `json:"ipv4Gateway"`
|
||||||
|
IPV6Subnet string `json:"ipv6Subnet"`
|
||||||
|
IPV6Gateway string `json:"ipv6Gateway"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
Attachable bool `json:"attachable"`
|
||||||
|
}
|
||||||
|
type NetworkCreat struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Driver string `json:"driver"`
|
||||||
|
Options []string `json:"options"`
|
||||||
|
IPV4Subnet string `json:"ipv4Subnet"`
|
||||||
|
IPV4Gateway string `json:"ipv4Gateway"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
Labels []string `json:"labels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Volume struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Labels []string `json:"labels"`
|
||||||
|
Driver string `json:"driver"`
|
||||||
|
Mountpoint string `json:"mountpoint"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
}
|
||||||
|
type VolumeCreat struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Driver string `json:"driver"`
|
||||||
|
Options []string `json:"options"`
|
||||||
|
Labels []string `json:"labels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BatchDelete struct {
|
||||||
|
Ids []string `json:"ids" validate:"required"`
|
||||||
|
}
|
||||||
|
@ -18,6 +18,12 @@ type ImageRemove struct {
|
|||||||
ImageName string `josn:"imageName" validate:"required"`
|
ImageName string `josn:"imageName" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ImageBuild struct {
|
||||||
|
From string `josn:"from" validate:"required"`
|
||||||
|
Dockerfile string `josn:"dockerfile" validate:"required"`
|
||||||
|
Tags string `josn:"tags" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type ImagePull struct {
|
type ImagePull struct {
|
||||||
RepoID uint `josn:"repoID"`
|
RepoID uint `josn:"repoID"`
|
||||||
ImageName string `josn:"imageName" validate:"required"`
|
ImageName string `josn:"imageName" validate:"required"`
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -12,6 +13,9 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/constant"
|
||||||
"github.com/1Panel-dev/1Panel/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/filters"
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,14 +23,21 @@ type ContainerService struct{}
|
|||||||
|
|
||||||
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)
|
||||||
|
PageVolume(req dto.PageInfo) (int64, interface{}, error)
|
||||||
ContainerOperation(req dto.ContainerOperation) error
|
ContainerOperation(req dto.ContainerOperation) error
|
||||||
ContainerLogs(param dto.ContainerLog) (string, error)
|
ContainerLogs(param dto.ContainerLog) (string, error)
|
||||||
ContainerInspect(id string) (string, error)
|
ContainerInspect(id string) (string, error)
|
||||||
|
DeleteNetwork(req dto.BatchDelete) error
|
||||||
|
CreateNetwork(req dto.NetworkCreat) error
|
||||||
|
DeleteVolume(req dto.BatchDelete) error
|
||||||
|
CreateVolume(req dto.VolumeCreat) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIContainerService() IContainerService {
|
func NewIContainerService() IContainerService {
|
||||||
return &ContainerService{}
|
return &ContainerService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
|
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
|
||||||
var (
|
var (
|
||||||
records []types.Container
|
records []types.Container
|
||||||
@ -138,3 +149,174 @@ func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) {
|
|||||||
}
|
}
|
||||||
return buf.String(), nil
|
return buf.String(), 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 (
|
||||||
|
ipv4 network.IPAMConfig
|
||||||
|
ipv6 network.IPAMConfig
|
||||||
|
)
|
||||||
|
if len(item.IPAM.Config) > 1 {
|
||||||
|
ipv4 = item.IPAM.Config[0]
|
||||||
|
ipv6 = item.IPAM.Config[1]
|
||||||
|
} else if len(item.IPAM.Config) > 0 {
|
||||||
|
ipv4 = 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,
|
||||||
|
IPV4Subnet: ipv4.Subnet,
|
||||||
|
IPV4Gateway: ipv4.Gateway,
|
||||||
|
IPV6Subnet: ipv6.Subnet,
|
||||||
|
IPV6Gateway: ipv6.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
|
||||||
|
}
|
||||||
|
ipv4 := network.IPAMConfig{
|
||||||
|
Subnet: req.IPV4Subnet,
|
||||||
|
Gateway: req.IPV4Gateway,
|
||||||
|
}
|
||||||
|
options := types.NetworkCreate{
|
||||||
|
Driver: req.Driver,
|
||||||
|
Scope: req.Scope,
|
||||||
|
IPAM: &network.IPAM{
|
||||||
|
Config: []network.IPAMConfig{ipv4},
|
||||||
|
},
|
||||||
|
Options: stringsToMap(req.Options),
|
||||||
|
Labels: stringsToMap(req.Labels),
|
||||||
|
}
|
||||||
|
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) 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 {
|
||||||
|
sps := strings.Split(label, "=")
|
||||||
|
if len(sps) > 1 {
|
||||||
|
lableMap[sps[0]] = sps[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lableMap
|
||||||
|
}
|
||||||
|
@ -73,6 +73,29 @@ func (u *ImageService) Page(req dto.PageInfo) (int64, interface{}, error) {
|
|||||||
return int64(total), backDatas, nil
|
return int64(total), backDatas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ImageService) ImageBuild(req dto.ImageBuild) error {
|
||||||
|
// client, err := docker.NewDockerClient()
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// if req.From == "path" {
|
||||||
|
// tar, err := archive.TarWithOptions("node-hello/", &archive.TarOptions{})
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// opts := types.ImageBuildOptions{
|
||||||
|
// Dockerfile: "Dockerfile",
|
||||||
|
// Tags: []string{dockerRegistryUserID + "/node-hello"},
|
||||||
|
// Remove: true,
|
||||||
|
// }
|
||||||
|
// if _, err := client.ImageBuild(context.TODO(), tar, opts); err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ImageService) ImagePull(req dto.ImagePull) error {
|
func (u *ImageService) ImagePull(req dto.ImagePull) error {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -185,8 +208,10 @@ func (u *ImageService) ImagePush(req dto.ImagePush) error {
|
|||||||
options.RegistryAuth = authStr
|
options.RegistryAuth = authStr
|
||||||
}
|
}
|
||||||
newName := fmt.Sprintf("%s/%s", repo.DownloadUrl, req.TagName)
|
newName := fmt.Sprintf("%s/%s", repo.DownloadUrl, req.TagName)
|
||||||
if err := client.ImageTag(context.TODO(), req.ImageName, newName); err != nil {
|
if newName != req.ImageName {
|
||||||
return err
|
if err := client.ImageTag(context.TODO(), req.ImageName, newName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
out, err := client.ImagePush(context.TODO(), newName, options)
|
out, err := client.ImagePush(context.TODO(), newName, options)
|
||||||
|
@ -8,9 +8,14 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/constant"
|
||||||
"github.com/1Panel-dev/1Panel/utils/docker"
|
"github.com/1Panel-dev/1Panel/utils/docker"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestImage(t *testing.T) {
|
func TestImage(t *testing.T) {
|
||||||
@ -32,6 +37,28 @@ func TestImage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuild(t *testing.T) {
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
tar, err := archive.TarWithOptions("/Users/slooop/Documents/neeko/", &archive.TarOptions{})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := types.ImageBuildOptions{
|
||||||
|
Dockerfile: "Dockerfile",
|
||||||
|
Tags: []string{"neeko" + "/test"},
|
||||||
|
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) {
|
func TestDeam(t *testing.T) {
|
||||||
file, err := ioutil.ReadFile(constant.DaemonJsonDir)
|
file, err := ioutil.ReadFile(constant.DaemonJsonDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -62,3 +89,30 @@ func TestDeam(t *testing.T) {
|
|||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNetwork(t *testing.T) {
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
var data []dto.Volume
|
||||||
|
list, err := client.VolumeList(context.TODO(), filters.NewArgs())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
for _, item := range list.Volumes {
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fmt.Println(data)
|
||||||
|
}
|
||||||
|
@ -37,5 +37,12 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
|||||||
baRouter.POST("/image/save", baseApi.ImageSave)
|
baRouter.POST("/image/save", baseApi.ImageSave)
|
||||||
baRouter.POST("/image/load", baseApi.ImageLoad)
|
baRouter.POST("/image/load", baseApi.ImageLoad)
|
||||||
baRouter.POST("/image/remove", baseApi.ImageRemove)
|
baRouter.POST("/image/remove", baseApi.ImageRemove)
|
||||||
|
|
||||||
|
baRouter.POST("/network/del", baseApi.DeleteNetwork)
|
||||||
|
baRouter.POST("/network/search", baseApi.SearchNetwork)
|
||||||
|
baRouter.POST("/network", baseApi.CreateNetwork)
|
||||||
|
baRouter.POST("/volume/del", baseApi.DeleteVolume)
|
||||||
|
baRouter.POST("/volume/search", baseApi.SearchVolume)
|
||||||
|
baRouter.POST("/volume", baseApi.CreateVolume)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
import { ReqPage } from '.';
|
|
||||||
|
|
||||||
export namespace Container {
|
export namespace Container {
|
||||||
export interface ContainerOperate {
|
export interface ContainerOperate {
|
||||||
containerID: string;
|
containerID: string;
|
||||||
operation: string;
|
operation: string;
|
||||||
newName: string;
|
newName: string;
|
||||||
}
|
}
|
||||||
export interface ContainerSearch extends ReqPage {
|
|
||||||
status: string;
|
|
||||||
}
|
|
||||||
export interface ContainerInfo {
|
export interface ContainerInfo {
|
||||||
containerID: string;
|
containerID: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -29,6 +24,10 @@ export namespace Container {
|
|||||||
version: string;
|
version: string;
|
||||||
size: string;
|
size: string;
|
||||||
}
|
}
|
||||||
|
export interface ImageBuild {
|
||||||
|
from: string;
|
||||||
|
dockerfile: string;
|
||||||
|
}
|
||||||
export interface ImagePull {
|
export interface ImagePull {
|
||||||
repoID: number;
|
repoID: number;
|
||||||
imageName: string;
|
imageName: string;
|
||||||
@ -50,6 +49,43 @@ export namespace Container {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NetworkInfo {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
labels: Array<string>;
|
||||||
|
driver: string;
|
||||||
|
ipamDriver: string;
|
||||||
|
ipv4Subnet: string;
|
||||||
|
ipv4Gateway: string;
|
||||||
|
ipv6Subnet: string;
|
||||||
|
ipv6Gateway: string;
|
||||||
|
createdAt: string;
|
||||||
|
attachable: string;
|
||||||
|
}
|
||||||
|
export interface NetworkCreate {
|
||||||
|
name: string;
|
||||||
|
labels: Array<string>;
|
||||||
|
options: Array<string>;
|
||||||
|
driver: string;
|
||||||
|
ipv4Subnet: string;
|
||||||
|
ipv4Gateway: string;
|
||||||
|
scope: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VolumeInfo {
|
||||||
|
name: string;
|
||||||
|
labels: Array<string>;
|
||||||
|
driver: string;
|
||||||
|
mountpoint: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
export interface VolumeCreate {
|
||||||
|
name: string;
|
||||||
|
driver: string;
|
||||||
|
options: Array<string>;
|
||||||
|
label: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RepoCreate {
|
export interface RepoCreate {
|
||||||
name: string;
|
name: string;
|
||||||
downloadUrl: string;
|
downloadUrl: string;
|
||||||
@ -81,4 +117,8 @@ export namespace Container {
|
|||||||
name: string;
|
name: string;
|
||||||
downloadUrl: string;
|
downloadUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BatchDelete {
|
||||||
|
ids: Array<string>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import http from '@/api';
|
|||||||
import { ResPage, ReqPage } from '../interface';
|
import { ResPage, ReqPage } from '../interface';
|
||||||
import { Container } from '../interface/container';
|
import { Container } from '../interface/container';
|
||||||
|
|
||||||
export const getContainerPage = (params: Container.ContainerSearch) => {
|
export const getContainerPage = (params: ReqPage) => {
|
||||||
return http.post<ResPage<Container.ContainerInfo>>(`/containers/search`, params);
|
return http.post<ResPage<Container.ContainerInfo>>(`/containers/search`, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,6 +22,9 @@ export const getContainerInspect = (containerID: string) => {
|
|||||||
export const getImagePage = (params: ReqPage) => {
|
export const getImagePage = (params: ReqPage) => {
|
||||||
return http.post<ResPage<Container.ImageInfo>>(`/containers/image/search`, params);
|
return http.post<ResPage<Container.ImageInfo>>(`/containers/image/search`, params);
|
||||||
};
|
};
|
||||||
|
export const imageBuild = (params: Container.ImageBuild) => {
|
||||||
|
return http.post<string>(`/containers/image/build`, params);
|
||||||
|
};
|
||||||
export const imagePull = (params: Container.ImagePull) => {
|
export const imagePull = (params: Container.ImagePull) => {
|
||||||
return http.post<string>(`/containers/image/pull`, params);
|
return http.post<string>(`/containers/image/pull`, params);
|
||||||
};
|
};
|
||||||
@ -38,6 +41,28 @@ export const imageRemove = (params: Container.ImageRemove) => {
|
|||||||
return http.post(`/containers/image/remove`, params);
|
return http.post(`/containers/image/remove`, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// network
|
||||||
|
export const getNetworkPage = (params: ReqPage) => {
|
||||||
|
return http.post<ResPage<Container.NetworkInfo>>(`/containers/network/search`, params);
|
||||||
|
};
|
||||||
|
export const deleteNetwork = (params: Container.BatchDelete) => {
|
||||||
|
return http.post(`/containers/network/del`, params);
|
||||||
|
};
|
||||||
|
export const createNetwork = (params: Container.NetworkCreate) => {
|
||||||
|
return http.post(`/containers/network`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// volume
|
||||||
|
export const getVolumePage = (params: ReqPage) => {
|
||||||
|
return http.post<ResPage<Container.VolumeInfo>>(`/containers/volume/search`, params);
|
||||||
|
};
|
||||||
|
export const deleteVolume = (params: Container.BatchDelete) => {
|
||||||
|
return http.post(`/containers/volume/del`, params);
|
||||||
|
};
|
||||||
|
export const createVolume = (params: Container.VolumeCreate) => {
|
||||||
|
return http.post(`/containers/volume`, params);
|
||||||
|
};
|
||||||
|
|
||||||
// repo
|
// repo
|
||||||
export const getRepoPage = (params: ReqPage) => {
|
export const getRepoPage = (params: ReqPage) => {
|
||||||
return http.post<ResPage<Container.RepoInfo>>(`/containers/repo/search`, params);
|
return http.post<ResPage<Container.RepoInfo>>(`/containers/repo/search`, params);
|
||||||
|
@ -158,7 +158,6 @@ export default {
|
|||||||
reName: '重命名',
|
reName: '重命名',
|
||||||
remove: '移除',
|
remove: '移除',
|
||||||
container: '容器',
|
container: '容器',
|
||||||
network: '网络',
|
|
||||||
storage: '数据卷',
|
storage: '数据卷',
|
||||||
schedule: '编排',
|
schedule: '编排',
|
||||||
upTime: '运行时长',
|
upTime: '运行时长',
|
||||||
@ -178,6 +177,8 @@ export default {
|
|||||||
importImage: '导入镜像',
|
importImage: '导入镜像',
|
||||||
import: '导入',
|
import: '导入',
|
||||||
build: '构建镜像',
|
build: '构建镜像',
|
||||||
|
edit: '编辑',
|
||||||
|
pathSelect: '路径选择',
|
||||||
label: '标签',
|
label: '标签',
|
||||||
push: '推送',
|
push: '推送',
|
||||||
fileName: '文件名',
|
fileName: '文件名',
|
||||||
@ -186,6 +187,17 @@ export default {
|
|||||||
version: '版本',
|
version: '版本',
|
||||||
size: '大小',
|
size: '大小',
|
||||||
from: '来源',
|
from: '来源',
|
||||||
|
tag: '标签',
|
||||||
|
|
||||||
|
network: '网络',
|
||||||
|
createNetwork: '添加网络',
|
||||||
|
networkName: '网络名',
|
||||||
|
driver: '模式',
|
||||||
|
option: '参数',
|
||||||
|
attachable: '可用',
|
||||||
|
subnet: '子网',
|
||||||
|
scope: 'IP 范围',
|
||||||
|
gateway: '网关',
|
||||||
|
|
||||||
repo: '仓库',
|
repo: '仓库',
|
||||||
name: '名称',
|
name: '名称',
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<el-button @click="onOpenload">
|
<el-button @click="onOpenload">
|
||||||
{{ $t('container.importImage') }}
|
{{ $t('container.importImage') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="onBatchDelete(null)">
|
<el-button @click="onOpenBuild">
|
||||||
{{ $t('container.build') }}
|
{{ $t('container.build') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="danger" plain :disabled="selects.length === 0" @click="onBatchDelete(null)">
|
<el-button type="danger" plain :disabled="selects.length === 0" @click="onBatchDelete(null)">
|
||||||
@ -30,6 +30,41 @@
|
|||||||
</ComplexTable>
|
</ComplexTable>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
<el-dialog v-model="buildVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="50%">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>{{ $t('container.importImage') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="buildFormRef" :model="buildForm" label-position="left" label-width="80px">
|
||||||
|
<el-form-item label="Dockerfile" :rules="Rules.requiredSelect" prop="from">
|
||||||
|
<el-radio-group v-model="buildForm.from">
|
||||||
|
<el-radio label="edit">{{ $t('container.edit') }}</el-radio>
|
||||||
|
<el-radio label="path">{{ $t('container.pathSelect') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="buildForm.from !== 'edit'" :rules="Rules.requiredInput">
|
||||||
|
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 10 }" v-model="buildForm.dockerfile" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-else :rules="Rules.requiredInput">
|
||||||
|
<el-input clearable v-model="buildForm.dockerfile">
|
||||||
|
<template #append>
|
||||||
|
<FileList @choose="loadBuildDir" :dir="true"></FileList>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.tag')">
|
||||||
|
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" v-model="buildForm.tag" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="submitBuild(buildFormRef)">{{ $t('container.import') }}</el-button>
|
||||||
|
<el-button @click="buildVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog v-model="pullVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="30%">
|
<el-dialog v-model="pullVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="30%">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@ -164,6 +199,7 @@ import { Container } from '@/api/interface/container';
|
|||||||
import {
|
import {
|
||||||
getImagePage,
|
getImagePage,
|
||||||
getRepoOption,
|
getRepoOption,
|
||||||
|
imageBuild,
|
||||||
imageLoad,
|
imageLoad,
|
||||||
imagePull,
|
imagePull,
|
||||||
imagePush,
|
imagePush,
|
||||||
@ -186,6 +222,15 @@ const paginationConfig = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
type FormInstance = InstanceType<typeof ElForm>;
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
|
||||||
|
const buildVisiable = ref(false);
|
||||||
|
const buildFormRef = ref<FormInstance>();
|
||||||
|
const buildForm = reactive({
|
||||||
|
from: 'path',
|
||||||
|
dockerfile: '',
|
||||||
|
tag: '',
|
||||||
|
});
|
||||||
|
|
||||||
const pullVisiable = ref(false);
|
const pullVisiable = ref(false);
|
||||||
const pullFormRef = ref<FormInstance>();
|
const pullFormRef = ref<FormInstance>();
|
||||||
const pullForm = reactive({
|
const pullForm = reactive({
|
||||||
@ -233,6 +278,9 @@ const loadRepos = async () => {
|
|||||||
repos.value = res.data;
|
repos.value = res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadBuildDir = async (path: string) => {
|
||||||
|
buildForm.dockerfile = path;
|
||||||
|
};
|
||||||
const loadSaveDir = async (path: string) => {
|
const loadSaveDir = async (path: string) => {
|
||||||
saveForm.path = path;
|
saveForm.path = path;
|
||||||
};
|
};
|
||||||
@ -284,6 +332,29 @@ const submitPush = async (formEl: FormInstance | undefined) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onOpenBuild = () => {
|
||||||
|
buildVisiable.value = true;
|
||||||
|
buildForm.from = 'path';
|
||||||
|
buildForm.dockerfile = '';
|
||||||
|
};
|
||||||
|
const submitBuild = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
loadVisiable.value = false;
|
||||||
|
await imageBuild(buildForm);
|
||||||
|
loading.value = false;
|
||||||
|
search();
|
||||||
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
} catch {
|
||||||
|
loading.value = false;
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onOpenload = () => {
|
const onOpenload = () => {
|
||||||
loadVisiable.value = true;
|
loadVisiable.value = true;
|
||||||
loadForm.path = '';
|
loadForm.path = '';
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
<Container v-if="activeNames === 'container'" />
|
<Container v-if="activeNames === 'container'" />
|
||||||
<Repo v-if="activeNames === 'repo'" />
|
<Repo v-if="activeNames === 'repo'" />
|
||||||
<Image v-if="activeNames === 'image'" />
|
<Image v-if="activeNames === 'image'" />
|
||||||
|
<Network v-if="activeNames === 'network'" />
|
||||||
<Monitor v-if="activeNames === 'storage'" />
|
<Monitor v-if="activeNames === 'storage'" />
|
||||||
<About v-if="activeNames === 'schedule'" />
|
<About v-if="activeNames === 'schedule'" />
|
||||||
</div>
|
</div>
|
||||||
@ -35,6 +36,7 @@ import { ref } from 'vue';
|
|||||||
import Container from '@/views/container/container/index.vue';
|
import Container from '@/views/container/container/index.vue';
|
||||||
import Repo from '@/views/container/repo/index.vue';
|
import Repo from '@/views/container/repo/index.vue';
|
||||||
import Image from '@/views/container/image/index.vue';
|
import Image from '@/views/container/image/index.vue';
|
||||||
|
import Network from '@/views/container/network/index.vue';
|
||||||
import Monitor from '@/views/setting/tabs/monitor.vue';
|
import Monitor from '@/views/setting/tabs/monitor.vue';
|
||||||
import About from '@/views/setting/tabs/about.vue';
|
import About from '@/views/setting/tabs/about.vue';
|
||||||
|
|
||||||
|
110
frontend/src/views/container/network/create/index.vue
Normal file
110
frontend/src/views/container/network/create/index.vue
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog v-model="createVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="30%">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>{{ $t('container.createNetwork') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||||
|
<el-form-item :label="$t('container.networkName')" prop="name">
|
||||||
|
<el-input clearable v-model="form.name" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.driver')" prop="driver">
|
||||||
|
<el-select v-model="form.driver">
|
||||||
|
<el-option label="bridge" value="bridge" />
|
||||||
|
<el-option label="ipvlan" value="ipvlan" />
|
||||||
|
<el-option label="macvlan" value="macvlan" />
|
||||||
|
<el-option label="overlay" value="overlay" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.option')" prop="optionStr">
|
||||||
|
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" v-model="form.optionStr" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.subnet')" prop="ipv4Subnet">
|
||||||
|
<el-input clearable v-model="form.ipv4Subnet" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.gateway')" prop="ipv4Gateway">
|
||||||
|
<el-input clearable v-model="form.ipv4Gateway" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.scope')" prop="scope">
|
||||||
|
<el-input clearable v-model="form.scope" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.tag')" prop="labelStr">
|
||||||
|
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" v-model="form.labelStr" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="createVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElForm, ElMessage } from 'element-plus';
|
||||||
|
import { createNetwork } from '@/api/modules/container';
|
||||||
|
|
||||||
|
const createVisiable = ref(false);
|
||||||
|
const form = reactive({
|
||||||
|
name: '',
|
||||||
|
labelStr: '',
|
||||||
|
labels: [] as Array<string>,
|
||||||
|
optionStr: '',
|
||||||
|
options: [] as Array<string>,
|
||||||
|
driver: '',
|
||||||
|
ipv4Subnet: '',
|
||||||
|
ipv4Gateway: '',
|
||||||
|
scope: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const acceptParams = (): void => {
|
||||||
|
createVisiable.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
name: [Rules.requiredInput, Rules.name],
|
||||||
|
driver: [Rules.requiredSelect],
|
||||||
|
ipv4Subnet: [Rules.requiredInput],
|
||||||
|
ipv4Gateway: [Rules.requiredInput],
|
||||||
|
scope: [Rules.requiredInput],
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
function restForm() {
|
||||||
|
if (formRef.value) {
|
||||||
|
formRef.value.resetFields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (form.labelStr !== '') {
|
||||||
|
form.labels = form.labelStr.split('\n');
|
||||||
|
}
|
||||||
|
if (form.optionStr !== '') {
|
||||||
|
form.options = form.optionStr.split('\n');
|
||||||
|
}
|
||||||
|
await createNetwork(form);
|
||||||
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
restForm();
|
||||||
|
emit('search');
|
||||||
|
createVisiable.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
140
frontend/src/views/container/network/index.vue
Normal file
140
frontend/src/views/container/network/index.vue
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-card style="margin-top: 20px">
|
||||||
|
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data" @search="search">
|
||||||
|
<template #toolbar>
|
||||||
|
<el-button style="margin-left: 10px" @click="onCreate()">
|
||||||
|
{{ $t('commons.button.create') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" plain :disabled="selects.length === 0" @click="batchDelete(null)">
|
||||||
|
{{ $t('commons.button.delete') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<el-table-column type="selection" fix />
|
||||||
|
<el-table-column
|
||||||
|
:label="$t('commons.table.name')"
|
||||||
|
show-overflow-tooltip
|
||||||
|
min-width="80"
|
||||||
|
prop="name"
|
||||||
|
fix
|
||||||
|
/>
|
||||||
|
<el-table-column :label="$t('container.driver')" show-overflow-tooltip min-width="40" prop="driver" />
|
||||||
|
<el-table-column :label="$t('container.attachable')" min-width="40" prop="attachable" fix>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-icon color="green" v-if="row.attachable"><Select /></el-icon>
|
||||||
|
<el-icon color="red" v-if="!row.attachable"><CloseBold /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('container.subnet')" min-width="80" fix>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div v-if="row.ipv4Subnet.length !== 0">
|
||||||
|
ipv4
|
||||||
|
<el-tag>{{ row.ipv4Subnet }}</el-tag>
|
||||||
|
</div>
|
||||||
|
<div v-if="row.ipv6Subnet.length !== 0">
|
||||||
|
ipv6
|
||||||
|
<el-tag>{{ row.ipv6Subnet }}</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('container.gateway')" min-width="80" fix>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div v-if="row.ipv4Gateway.length !== 0">
|
||||||
|
ipv4
|
||||||
|
<el-tag>{{ row.ipv4Gateway }}</el-tag>
|
||||||
|
</div>
|
||||||
|
<div v-if="row.ipv6Gateway.length !== 0">
|
||||||
|
ipv6
|
||||||
|
<el-tag>{{ row.ipv6Gateway }}</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('container.tag')" min-width="140" fix>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div v-for="(item, index) of row.labels" :key="index">
|
||||||
|
<el-tooltip class="item" :content="item" placement="top">
|
||||||
|
<el-tag>{{ item }}</el-tag>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="createdAt"
|
||||||
|
min-width="90"
|
||||||
|
:label="$t('commons.table.date')"
|
||||||
|
:formatter="dateFromat"
|
||||||
|
/>
|
||||||
|
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" fix />
|
||||||
|
</ComplexTable>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<CreateDialog @search="search" ref="dialogCreateRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ComplexTable from '@/components/complex-table/index.vue';
|
||||||
|
import CreateDialog from '@/views/container/network/create/index.vue';
|
||||||
|
import { reactive, onMounted, ref } from 'vue';
|
||||||
|
import { dateFromat } from '@/utils/util';
|
||||||
|
import { deleteNetwork, getNetworkPage } from '@/api/modules/container';
|
||||||
|
import { Container } from '@/api/interface/container';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||||
|
|
||||||
|
const data = ref();
|
||||||
|
const selects = ref<any>([]);
|
||||||
|
const paginationConfig = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialogCreateRef = ref<DialogExpose>();
|
||||||
|
|
||||||
|
interface DialogExpose {
|
||||||
|
acceptParams: () => void;
|
||||||
|
}
|
||||||
|
const onCreate = async () => {
|
||||||
|
dialogCreateRef.value!.acceptParams();
|
||||||
|
};
|
||||||
|
|
||||||
|
const search = async () => {
|
||||||
|
const params = {
|
||||||
|
page: paginationConfig.page,
|
||||||
|
pageSize: paginationConfig.pageSize,
|
||||||
|
};
|
||||||
|
await getNetworkPage(params).then((res) => {
|
||||||
|
if (res.data) {
|
||||||
|
data.value = res.data.items;
|
||||||
|
}
|
||||||
|
paginationConfig.total = res.data.total;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const batchDelete = async (row: Container.NetworkInfo | null) => {
|
||||||
|
let ids: Array<string> = [];
|
||||||
|
if (row === null) {
|
||||||
|
selects.value.forEach((item: Container.NetworkInfo) => {
|
||||||
|
ids.push(item.id);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ids.push(row.id);
|
||||||
|
}
|
||||||
|
await useDeleteData(deleteNetwork, { ids: ids }, 'commons.msg.delete', true);
|
||||||
|
search();
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttons = [
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.delete'),
|
||||||
|
click: (row: Container.NetworkInfo) => {
|
||||||
|
batchDelete(row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
search();
|
||||||
|
});
|
||||||
|
</script>
|
19
go.mod
19
go.mod
@ -22,7 +22,7 @@ require (
|
|||||||
github.com/gwatts/gin-adapter v1.0.0
|
github.com/gwatts/gin-adapter v1.0.0
|
||||||
github.com/jinzhu/copier v0.3.5
|
github.com/jinzhu/copier v0.3.5
|
||||||
github.com/joho/godotenv v1.4.0
|
github.com/joho/godotenv v1.4.0
|
||||||
github.com/kr/pty v1.1.1
|
github.com/kr/pty v1.1.5
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.7
|
github.com/mholt/archiver/v4 v4.0.0-alpha.7
|
||||||
github.com/minio/minio-go/v7 v7.0.36
|
github.com/minio/minio-go/v7 v7.0.36
|
||||||
github.com/mojocn/base64Captcha v1.3.5
|
github.com/mojocn/base64Captcha v1.3.5
|
||||||
@ -54,11 +54,15 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||||
|
github.com/Microsoft/hcsshim v0.9.4 // indirect
|
||||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
github.com/cespare/xxhash v1.1.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
|
github.com/containerd/cgroups v1.0.3 // indirect
|
||||||
|
github.com/containerd/containerd v1.6.8 // indirect
|
||||||
|
github.com/containerd/continuity v0.3.0 // indirect
|
||||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||||
github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb // indirect
|
github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb // indirect
|
||||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||||
@ -107,13 +111,15 @@ require (
|
|||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/moby/sys/mount v0.3.3 // indirect
|
||||||
|
github.com/moby/sys/mountinfo v0.6.2 // indirect
|
||||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
|
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
|
||||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
|
||||||
|
github.com/opencontainers/runc v1.1.4 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||||
@ -132,16 +138,17 @@ require (
|
|||||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
go.opencensus.io v0.23.0 // indirect
|
go.opencensus.io v0.23.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.0.0 // indirect
|
go.opentelemetry.io/otel v1.3.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.0.0 // indirect
|
go.opentelemetry.io/otel/trace v1.3.0 // indirect
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||||
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
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gotest.tools/v3 v3.3.0 // indirect
|
gotest.tools/v3 v3.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user