mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
feat: 容器创建功能实现
This commit is contained in:
parent
28df0b9a3c
commit
53845e60b6
@ -30,6 +30,23 @@ func (b *BaseApi) SearchContainer(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) ContainerCreate(c *gin.Context) {
|
||||||
|
var req dto.ContainerCreate
|
||||||
|
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.ContainerCreate(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BaseApi) ContainerOperation(c *gin.Context) {
|
func (b *BaseApi) ContainerOperation(c *gin.Context) {
|
||||||
var req dto.ContainerOperation
|
var req dto.ContainerOperation
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
@ -161,6 +178,14 @@ func (b *BaseApi) SearchVolume(c *gin.Context) {
|
|||||||
Total: total,
|
Total: total,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
func (b *BaseApi) ListVolume(c *gin.Context) {
|
||||||
|
list, err := containerService.ListVolume()
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, list)
|
||||||
|
}
|
||||||
func (b *BaseApi) DeleteVolume(c *gin.Context) {
|
func (b *BaseApi) DeleteVolume(c *gin.Context) {
|
||||||
var req dto.BatchDelete
|
var req dto.BatchDelete
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
@ -31,6 +31,15 @@ func (b *BaseApi) SearchImage(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) ListImage(c *gin.Context) {
|
||||||
|
list, err := imageService.List()
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, list)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BaseApi) ImageBuild(c *gin.Context) {
|
func (b *BaseApi) ImageBuild(c *gin.Context) {
|
||||||
var req dto.ImageBuild
|
var req dto.ImageBuild
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
@ -10,3 +10,7 @@ type Response struct {
|
|||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Option string `json:"option"`
|
||||||
|
}
|
||||||
|
@ -22,6 +22,31 @@ type ContainerInfo struct {
|
|||||||
RunTime string `json:"runTime"`
|
RunTime string `json:"runTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContainerCreate struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Image string `json:"image"`
|
||||||
|
PublishAllPorts bool `json:"publishAllPorts"`
|
||||||
|
ExposedPorts []PortHelper `json:"exposedPorts"`
|
||||||
|
Cmd []string `json:"cmd"`
|
||||||
|
NanoCPUs int64 `json:"nanoCPUs"`
|
||||||
|
Memory int64 `json:"memory"`
|
||||||
|
AutoRemove bool `json:"autoRemove"`
|
||||||
|
Volumes []VolumeHelper `json:"volumes"`
|
||||||
|
Labels []string `json:"labels"`
|
||||||
|
Env []string `json:"env"`
|
||||||
|
RestartPolicy string `json:"restartPolicy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VolumeHelper struct {
|
||||||
|
SourceDir string `json:"sourceDir"`
|
||||||
|
ContainerDir string `json:"containerDir"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
}
|
||||||
|
type PortHelper struct {
|
||||||
|
ContainerPort int `json:"containerPort"`
|
||||||
|
HostPort int `json:"hostPort"`
|
||||||
|
}
|
||||||
|
|
||||||
type ContainerLog struct {
|
type ContainerLog struct {
|
||||||
ContainerID string `json:"containerID" validate:"required"`
|
ContainerID string `json:"containerID" validate:"required"`
|
||||||
Mode string `json:"mode" validate:"required"`
|
Mode string `json:"mode" validate:"required"`
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -13,10 +14,13 @@ 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/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/api/types/volume"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
|
"github.com/docker/go-connections/nat"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContainerService struct{}
|
type ContainerService struct{}
|
||||||
@ -25,6 +29,8 @@ 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)
|
||||||
PageVolume(req dto.PageInfo) (int64, interface{}, error)
|
PageVolume(req dto.PageInfo) (int64, interface{}, error)
|
||||||
|
ListVolume() ([]dto.Options, error)
|
||||||
|
ContainerCreate(req dto.ContainerCreate) error
|
||||||
ContainerOperation(req dto.ContainerOperation) error
|
ContainerOperation(req dto.ContainerOperation) error
|
||||||
ContainerLogs(param dto.ContainerLog) (string, error)
|
ContainerLogs(param dto.ContainerLog) (string, error)
|
||||||
Inspect(req dto.InspectReq) (string, error)
|
Inspect(req dto.InspectReq) (string, error)
|
||||||
@ -101,6 +107,55 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
|||||||
return string(bytes), nil
|
return string(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config := &container.Config{
|
||||||
|
Image: req.Image,
|
||||||
|
Cmd: req.Cmd,
|
||||||
|
Env: req.Env,
|
||||||
|
Labels: stringsToMap(req.Labels),
|
||||||
|
}
|
||||||
|
hostConf := &container.HostConfig{
|
||||||
|
AutoRemove: req.AutoRemove,
|
||||||
|
PublishAllPorts: req.PublishAllPorts,
|
||||||
|
RestartPolicy: container.RestartPolicy{Name: req.RestartPolicy},
|
||||||
|
}
|
||||||
|
if req.RestartPolicy == "on-failure" {
|
||||||
|
hostConf.RestartPolicy.MaximumRetryCount = 5
|
||||||
|
}
|
||||||
|
if req.NanoCPUs != 0 {
|
||||||
|
hostConf.NanoCPUs = req.NanoCPUs * 1000000000
|
||||||
|
}
|
||||||
|
if req.Memory != 0 {
|
||||||
|
hostConf.Memory = req.Memory
|
||||||
|
}
|
||||||
|
if len(req.ExposedPorts) != 0 {
|
||||||
|
hostConf.PortBindings = make(nat.PortMap)
|
||||||
|
for _, port := range req.ExposedPorts {
|
||||||
|
bindItem := nat.PortBinding{HostPort: strconv.Itoa(port.HostPort)}
|
||||||
|
hostConf.PortBindings[nat.Port(fmt.Sprintf("%d/tcp", port.ContainerPort))] = []nat.PortBinding{bindItem}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(req.Volumes) != 0 {
|
||||||
|
config.Volumes = make(map[string]struct{})
|
||||||
|
for _, volume := range req.Volumes {
|
||||||
|
config.Volumes[volume.ContainerDir] = struct{}{}
|
||||||
|
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container, err := client.ContainerCreate(context.TODO(), config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := client.ContainerStart(context.TODO(), container.ID, types.ContainerStartOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("create successful but start failed, err: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
|
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
|
||||||
var err error
|
var err error
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@ -293,6 +348,23 @@ func (u *ContainerService) PageVolume(req dto.PageInfo) (int64, interface{}, err
|
|||||||
|
|
||||||
return int64(total), data, nil
|
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 {
|
func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -23,6 +23,7 @@ type ImageService struct{}
|
|||||||
|
|
||||||
type IImageService interface {
|
type IImageService interface {
|
||||||
Page(req dto.PageInfo) (int64, interface{}, error)
|
Page(req dto.PageInfo) (int64, interface{}, error)
|
||||||
|
List() ([]dto.Options, error)
|
||||||
ImagePull(req dto.ImagePull) error
|
ImagePull(req dto.ImagePull) error
|
||||||
ImageLoad(req dto.ImageLoad) error
|
ImageLoad(req dto.ImageLoad) error
|
||||||
ImageSave(req dto.ImageSave) error
|
ImageSave(req dto.ImageSave) error
|
||||||
@ -43,7 +44,7 @@ func (u *ImageService) Page(req dto.PageInfo) (int64, interface{}, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
list, err = client.ImageList(context.Background(), types.ImageListOptions{All: true})
|
list, err = client.ImageList(context.Background(), types.ImageListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
@ -70,6 +71,29 @@ func (u *ImageService) Page(req dto.PageInfo) (int64, interface{}, error) {
|
|||||||
return int64(total), backDatas, nil
|
return int64(total), backDatas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ImageService) List() ([]dto.Options, error) {
|
||||||
|
var (
|
||||||
|
list []types.ImageSummary
|
||||||
|
backDatas []dto.Options
|
||||||
|
)
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list, err = client.ImageList(context.Background(), types.ImageListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, image := range list {
|
||||||
|
for _, tag := range image.RepoTags {
|
||||||
|
backDatas = append(backDatas, dto.Options{
|
||||||
|
Option: tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return backDatas, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
|
func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
113
backend/app/service/image_test.go
Normal file
113
backend/app/service/image_test.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
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/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)
|
||||||
|
}
|
||||||
|
_, err = client.NetworkCreate(context.TODO(), "test", types.NetworkCreate{})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
|||||||
{
|
{
|
||||||
baRouter.POST("/search", baseApi.SearchContainer)
|
baRouter.POST("/search", baseApi.SearchContainer)
|
||||||
baRouter.POST("/inspect", baseApi.Inspect)
|
baRouter.POST("/inspect", baseApi.Inspect)
|
||||||
|
baRouter.POST("", baseApi.ContainerCreate)
|
||||||
withRecordRouter.POST("operate", baseApi.ContainerOperation)
|
withRecordRouter.POST("operate", baseApi.ContainerOperation)
|
||||||
withRecordRouter.POST("/log", baseApi.ContainerLogs)
|
withRecordRouter.POST("/log", baseApi.ContainerLogs)
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
|||||||
withRecordRouter.POST("/repo/del", baseApi.DeleteRepo)
|
withRecordRouter.POST("/repo/del", baseApi.DeleteRepo)
|
||||||
|
|
||||||
baRouter.POST("/image/search", baseApi.SearchImage)
|
baRouter.POST("/image/search", baseApi.SearchImage)
|
||||||
|
baRouter.GET("/image", baseApi.ListImage)
|
||||||
baRouter.POST("/image/pull", baseApi.ImagePull)
|
baRouter.POST("/image/pull", baseApi.ImagePull)
|
||||||
baRouter.POST("/image/push", baseApi.ImagePush)
|
baRouter.POST("/image/push", baseApi.ImagePush)
|
||||||
baRouter.POST("/image/save", baseApi.ImageSave)
|
baRouter.POST("/image/save", baseApi.ImageSave)
|
||||||
@ -45,6 +47,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
|||||||
baRouter.POST("/network", baseApi.CreateNetwork)
|
baRouter.POST("/network", baseApi.CreateNetwork)
|
||||||
baRouter.POST("/volume/del", baseApi.DeleteVolume)
|
baRouter.POST("/volume/del", baseApi.DeleteVolume)
|
||||||
baRouter.POST("/volume/search", baseApi.SearchVolume)
|
baRouter.POST("/volume/search", baseApi.SearchVolume)
|
||||||
|
baRouter.GET("/volume", baseApi.ListVolume)
|
||||||
baRouter.POST("/volume", baseApi.CreateVolume)
|
baRouter.POST("/volume", baseApi.CreateVolume)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,31 @@ export namespace Container {
|
|||||||
operation: string;
|
operation: string;
|
||||||
newName: string;
|
newName: string;
|
||||||
}
|
}
|
||||||
|
export interface ContainerCreate {
|
||||||
|
name: string;
|
||||||
|
image: string;
|
||||||
|
cmd: Array<string>;
|
||||||
|
publishAllPorts: boolean;
|
||||||
|
exposedPorts: Array<Port>;
|
||||||
|
nanoCPUs: number;
|
||||||
|
memory: number;
|
||||||
|
volumes: Array<Volume>;
|
||||||
|
autoRemove: boolean;
|
||||||
|
labels: Array<string>;
|
||||||
|
labelsStr: string;
|
||||||
|
env: Array<string>;
|
||||||
|
envStr: string;
|
||||||
|
restartPolicy: string;
|
||||||
|
}
|
||||||
|
export interface Port {
|
||||||
|
containerPort: number;
|
||||||
|
hostPort: number;
|
||||||
|
}
|
||||||
|
export interface Volume {
|
||||||
|
sourceDir: string;
|
||||||
|
containerDir: string;
|
||||||
|
mode: string;
|
||||||
|
}
|
||||||
export interface ContainerInfo {
|
export interface ContainerInfo {
|
||||||
containerID: string;
|
containerID: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -20,6 +45,9 @@ export namespace Container {
|
|||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
export interface Options {
|
||||||
|
option: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ImageInfo {
|
export interface ImageInfo {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -5,6 +5,9 @@ import { Container } from '../interface/container';
|
|||||||
export const getContainerPage = (params: ReqPage) => {
|
export const getContainerPage = (params: ReqPage) => {
|
||||||
return http.post<ResPage<Container.ContainerInfo>>(`/containers/search`, params);
|
return http.post<ResPage<Container.ContainerInfo>>(`/containers/search`, params);
|
||||||
};
|
};
|
||||||
|
export const createContainer = (params: Container.ContainerCreate) => {
|
||||||
|
return http.post(`/containers`, params);
|
||||||
|
};
|
||||||
|
|
||||||
export const getContainerLog = (params: Container.ContainerLogSearch) => {
|
export const getContainerLog = (params: Container.ContainerLogSearch) => {
|
||||||
return http.post<string>(`/containers/log`, params);
|
return http.post<string>(`/containers/log`, params);
|
||||||
@ -22,6 +25,9 @@ export const inspect = (params: Container.ContainerInspect) => {
|
|||||||
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 imageOptions = () => {
|
||||||
|
return http.get<Array<Container.Options>>(`/containers/image`);
|
||||||
|
};
|
||||||
export const imageBuild = (params: Container.ImageBuild) => {
|
export const imageBuild = (params: Container.ImageBuild) => {
|
||||||
return http.post<string>(`/containers/image/build`, params);
|
return http.post<string>(`/containers/image/build`, params);
|
||||||
};
|
};
|
||||||
@ -59,6 +65,9 @@ export const createNetwork = (params: Container.NetworkCreate) => {
|
|||||||
export const getVolumePage = (params: ReqPage) => {
|
export const getVolumePage = (params: ReqPage) => {
|
||||||
return http.post<ResPage<Container.VolumeInfo>>(`/containers/volume/search`, params);
|
return http.post<ResPage<Container.VolumeInfo>>(`/containers/volume/search`, params);
|
||||||
};
|
};
|
||||||
|
export const volumeOptions = () => {
|
||||||
|
return http.get<Array<Container.Options>>(`/containers/volume`);
|
||||||
|
};
|
||||||
export const deleteVolume = (params: Container.BatchDelete) => {
|
export const deleteVolume = (params: Container.BatchDelete) => {
|
||||||
return http.post(`/containers/volume/del`, params);
|
return http.post(`/containers/volume/del`, params);
|
||||||
};
|
};
|
||||||
|
@ -169,6 +169,30 @@ export default {
|
|||||||
lastHour: 'Last Hour',
|
lastHour: 'Last Hour',
|
||||||
last10Min: 'Last 10 Minutes',
|
last10Min: 'Last 10 Minutes',
|
||||||
|
|
||||||
|
containerCreate: 'Container create',
|
||||||
|
port: 'Port',
|
||||||
|
exposePort: 'Expose port',
|
||||||
|
exposeAll: 'Expose all',
|
||||||
|
containerPort: 'Container port',
|
||||||
|
serverPort: 'Host port',
|
||||||
|
cmd: 'Command',
|
||||||
|
cmdHelper: 'one in a row, for example, echo "hello"',
|
||||||
|
autoRemove: 'Auto remove',
|
||||||
|
cpuQuota: 'NacosCPU',
|
||||||
|
memoryLimit: 'Memory',
|
||||||
|
limitHelper: 'If the limit is 0, the limit is turned off',
|
||||||
|
mount: 'Mount',
|
||||||
|
serverPath: 'Server path',
|
||||||
|
containerDir: 'Container path',
|
||||||
|
modeRW: 'Read-Write',
|
||||||
|
modeR: 'Read-Only',
|
||||||
|
mode: 'Mode',
|
||||||
|
env: 'Environment',
|
||||||
|
restartPolicy: 'Restart policy',
|
||||||
|
unlessStopped: 'unless-stopped',
|
||||||
|
onFailure: 'on-failure(five times by default)',
|
||||||
|
no: 'no',
|
||||||
|
|
||||||
image: 'Image',
|
image: 'Image',
|
||||||
imagePull: 'Image pull',
|
imagePull: 'Image pull',
|
||||||
imagePush: 'Image push',
|
imagePush: 'Image push',
|
||||||
@ -179,6 +203,7 @@ export default {
|
|||||||
importImage: 'Image import',
|
importImage: 'Image import',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
build: 'Build',
|
build: 'Build',
|
||||||
|
imageBuild: 'Image build',
|
||||||
label: 'Label',
|
label: 'Label',
|
||||||
push: 'Push',
|
push: 'Push',
|
||||||
fileName: 'FileName',
|
fileName: 'FileName',
|
||||||
@ -187,6 +212,9 @@ export default {
|
|||||||
version: 'Version',
|
version: 'Version',
|
||||||
size: 'Size',
|
size: 'Size',
|
||||||
from: 'From',
|
from: 'From',
|
||||||
|
tag: 'Tag',
|
||||||
|
tagHelper: 'one in a row, for example, key=value',
|
||||||
|
imageNameHelper: 'Image name and Tag, for example: nginx:latest',
|
||||||
|
|
||||||
network: 'Network',
|
network: 'Network',
|
||||||
createNetwork: 'Create network',
|
createNetwork: 'Create network',
|
||||||
|
@ -166,6 +166,30 @@ export default {
|
|||||||
lastHour: '最近 1 小时',
|
lastHour: '最近 1 小时',
|
||||||
last10Min: '最近 10 分钟',
|
last10Min: '最近 10 分钟',
|
||||||
|
|
||||||
|
containerCreate: '容器创建',
|
||||||
|
port: '端口',
|
||||||
|
exposePort: '暴露端口',
|
||||||
|
exposeAll: '暴露所有',
|
||||||
|
containerPort: '容器端口',
|
||||||
|
serverPort: '服务器端口',
|
||||||
|
cmd: '启动命令',
|
||||||
|
cmdHelper: '一行一个,例: echo "hello"',
|
||||||
|
autoRemove: '容器退出后自动删除容器',
|
||||||
|
cpuQuota: 'CPU 限制',
|
||||||
|
memoryLimit: '内存限制',
|
||||||
|
limitHelper: '限制为 0 则关闭限制',
|
||||||
|
mount: '挂载卷',
|
||||||
|
serverPath: '服务器目录',
|
||||||
|
containerDir: '容器目录',
|
||||||
|
modeRW: '读写',
|
||||||
|
modeR: '只读',
|
||||||
|
mode: '权限',
|
||||||
|
env: '环境变量',
|
||||||
|
restartPolicy: '重启规则',
|
||||||
|
unlessStopped: '关闭后重启',
|
||||||
|
onFailure: '失败后重启(默认重启 5 次)',
|
||||||
|
no: '不重启',
|
||||||
|
|
||||||
image: '镜像',
|
image: '镜像',
|
||||||
imagePull: '拉取镜像',
|
imagePull: '拉取镜像',
|
||||||
imagePush: '推送镜像',
|
imagePush: '推送镜像',
|
||||||
@ -176,6 +200,7 @@ export default {
|
|||||||
path: '路径',
|
path: '路径',
|
||||||
importImage: '导入镜像',
|
importImage: '导入镜像',
|
||||||
import: '导入',
|
import: '导入',
|
||||||
|
imageBuild: '构建镜像',
|
||||||
build: '构建镜像',
|
build: '构建镜像',
|
||||||
edit: '编辑',
|
edit: '编辑',
|
||||||
pathSelect: '路径选择',
|
pathSelect: '路径选择',
|
||||||
@ -188,6 +213,8 @@ export default {
|
|||||||
size: '大小',
|
size: '大小',
|
||||||
from: '来源',
|
from: '来源',
|
||||||
tag: '标签',
|
tag: '标签',
|
||||||
|
tagHelper: '一行一个,例: key=value',
|
||||||
|
imageNameHelper: '镜像名称及 Tag,例:nginx:latest',
|
||||||
|
|
||||||
network: '网络',
|
network: '网络',
|
||||||
createNetwork: '添加网络',
|
createNetwork: '添加网络',
|
||||||
|
@ -2,33 +2,57 @@
|
|||||||
<el-dialog v-model="createVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="50%">
|
<el-dialog v-model="createVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="50%">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>容器创建</span>
|
<span>{{ $t('container.containerCreate') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-form ref="formRef" :model="form" label-position="left" :rules="rules" label-width="120px">
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||||
<el-form-item label="容器名称" prop="name">
|
<el-form-item :label="$t('container.name')" prop="name">
|
||||||
<el-input clearable v-model="form.name" />
|
<el-input clearable v-model="form.name" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="镜像" prop="image">
|
<el-form-item :label="$t('container.image')" prop="image">
|
||||||
<el-input clearable v-model="form.image" />
|
<el-select style="width: 100%" filterable v-model="form.image">
|
||||||
|
<el-option v-for="(item, index) of images" :key="index" :value="item.option" :label="item.option" />
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="端口" prop="image">
|
<el-form-item :label="$t('container.port')">
|
||||||
<el-radio-group v-model="form.publishAllPorts" class="ml-4">
|
<el-radio-group v-model="form.publishAllPorts" class="ml-4">
|
||||||
<el-radio :label="false">暴露端口</el-radio>
|
<el-radio :label="false">{{ $t('container.exposePort') }}</el-radio>
|
||||||
<el-radio :label="true">暴露所有</el-radio>
|
<el-radio :label="true">{{ $t('container.exposeAll') }}</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
<div style="margin-top: 20px"></div>
|
<el-form-item v-if="!form.publishAllPorts">
|
||||||
<table style="width: 100%; margin-top: 5px" class="tab-table">
|
<el-card style="width: 100%">
|
||||||
<tr v-for="(row, index) in ports" :key="index">
|
<table style="width: 100%" class="tab-table">
|
||||||
|
<tr v-if="form.exposedPorts.length !== 0">
|
||||||
|
<th scope="col" width="48%" align="left">
|
||||||
|
<label>{{ $t('container.containerPort') }}</label>
|
||||||
|
</th>
|
||||||
|
<th scope="col" width="48%" align="left">
|
||||||
|
<label>{{ $t('container.serverPort') }}</label>
|
||||||
|
</th>
|
||||||
|
<th align="left"></th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(row, index) in form.exposedPorts" :key="index">
|
||||||
<td width="48%">
|
<td width="48%">
|
||||||
<el-input v-model="row['key']" />
|
<el-input-number
|
||||||
|
:min="0"
|
||||||
|
:max="65535"
|
||||||
|
style="width: 100%"
|
||||||
|
controls-position="right"
|
||||||
|
v-model.number="row.containerPort"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td width="48%">
|
<td width="48%">
|
||||||
<el-input v-model="row['value']" />
|
<el-input-number
|
||||||
|
:min="0"
|
||||||
|
:max="65535"
|
||||||
|
style="width: 100%"
|
||||||
|
controls-position="right"
|
||||||
|
v-model.number="row.hostPort"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<el-button type="text" style="font-size: 10px" @click="handlePortsDelete(index)">
|
<el-button link style="font-size: 10px" @click="handlePortsDelete(index)">
|
||||||
{{ $t('commons.button.delete') }}
|
{{ $t('commons.button.delete') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</td>
|
</td>
|
||||||
@ -39,34 +63,80 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
</el-card>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="启动命令" prop="command">
|
<el-form-item :label="$t('container.cmd')" prop="cmdStr">
|
||||||
<el-input clearable v-model="form.command" />
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="$t('container.cmdHelper')"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
v-model="form.cmdStr"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="autoRemove">
|
<el-form-item prop="autoRemove">
|
||||||
<el-checkbox v-model="form.autoRemove">容器停止后自动删除容器</el-checkbox>
|
<el-checkbox v-model="form.autoRemove">{{ $t('container.autoRemove') }}</el-checkbox>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="限制CPU" prop="cpusetCpus">
|
<el-form-item :label="$t('container.cpuQuota')" prop="nanoCPUs">
|
||||||
<el-input v-model="form.cpusetCpus" />
|
<el-input type="number" style="width: 40%" v-model.number="form.nanoCPUs">
|
||||||
|
<template #append><div style="width: 60px">Core</div></template>
|
||||||
|
</el-input>
|
||||||
|
<span class="input-help">{{ $t('container.limitHelper') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="内存" prop="memeryLimit">
|
<el-form-item :label="$t('container.memoryLimit')" prop="memoryItem">
|
||||||
<el-input v-model="form.memeryLimit" />
|
<el-input style="width: 40%" v-model.number="form.memoryItem">
|
||||||
|
<template #append>
|
||||||
|
<el-select v-model="form.memoryUnit" placeholder="Select" style="width: 100px">
|
||||||
|
<el-option label="KB" value="KB" />
|
||||||
|
<el-option label="MB" value="MB" />
|
||||||
|
<el-option label="GB" value="GB" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<span class="input-help">{{ $t('container.limitHelper') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="挂载卷">
|
<el-form-item :label="$t('container.mount')">
|
||||||
<div style="margin-top: 20px"></div>
|
<el-card style="width: 100%">
|
||||||
<table style="width: 100%; margin-top: 5px" class="tab-table">
|
<table style="width: 100%" class="tab-table">
|
||||||
<tr v-for="(row, index) in volumes" :key="index">
|
<tr v-if="form.volumes.length !== 0">
|
||||||
<td width="30%">
|
<th scope="col" width="32%" align="left">
|
||||||
<el-input v-model="row['name']" />
|
<label>{{ $t('container.serverPath') }}</label>
|
||||||
|
</th>
|
||||||
|
<th scope="col" width="32%" align="left">
|
||||||
|
<label>{{ $t('container.mode') }}</label>
|
||||||
|
</th>
|
||||||
|
<th scope="col" width="32%" align="left">
|
||||||
|
<label>{{ $t('container.containerDir') }}</label>
|
||||||
|
</th>
|
||||||
|
<th align="left"></th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(row, index) in form.volumes" :key="index">
|
||||||
|
<td width="32%">
|
||||||
|
<el-select
|
||||||
|
style="width: 100%"
|
||||||
|
allow-create
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
v-model="row.sourceDir"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="(item, indexV) of volumes"
|
||||||
|
:key="indexV"
|
||||||
|
:value="item.option"
|
||||||
|
:label="item.option"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
</td>
|
</td>
|
||||||
<td width="30%">
|
<td width="32%">
|
||||||
<el-input v-model="row['bind']" />
|
<el-select style="width: 100%" filterable v-model="row.mode">
|
||||||
|
<el-option value="rw" :label="$t('container.modeRW')" />
|
||||||
|
<el-option value="ro" :label="$t('container.modeR')" />
|
||||||
|
</el-select>
|
||||||
</td>
|
</td>
|
||||||
<td width="30%">
|
<td width="32%">
|
||||||
<el-input v-model="row['mode']" />
|
<el-input v-model="row.containerDir" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<el-button type="text" style="font-size: 10px" @click="handleVolumesDelete(index)">
|
<el-button link style="font-size: 10px" @click="handleVolumesDelete(index)">
|
||||||
{{ $t('commons.button.delete') }}
|
{{ $t('commons.button.delete') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</td>
|
</td>
|
||||||
@ -77,18 +147,29 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
</el-card>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="标签" prop="labels">
|
<el-form-item :label="$t('container.tag')" prop="labelsStr">
|
||||||
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" v-model="form.labels" />
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="$t('container.tagHelper')"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
v-model="form.labelsStr"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="环境变量(每行一个)" prop="environment">
|
<el-form-item :label="$t('container.env')" prop="envStr">
|
||||||
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" v-model="form.environment" />
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="$t('container.tagHelper')"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
v-model="form.envStr"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="重启规则" prop="restartPolicy.value">
|
<el-form-item :label="$t('container.restartPolicy')" prop="restartPolicy">
|
||||||
<el-radio-group v-model="form.restartPolicy.value">
|
<el-radio-group v-model="form.restartPolicy">
|
||||||
<el-radio :label="false">关闭后马上重启</el-radio>
|
<el-radio label="unless-stopped">{{ $t('container.unlessStopped') }}</el-radio>
|
||||||
<el-radio :label="false">错误时重启(默认重启 5 次)</el-radio>
|
<el-radio label="on-failure">{{ $t('container.onFailure') }}</el-radio>
|
||||||
<el-radio :label="true">不重启</el-radio>
|
<el-radio label="no">{{ $t('container.no') }}</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -108,51 +189,47 @@ import { reactive, ref } from 'vue';
|
|||||||
import { Rules } from '@/global/form-rules';
|
import { Rules } from '@/global/form-rules';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { ElForm, ElMessage } from 'element-plus';
|
import { ElForm, ElMessage } from 'element-plus';
|
||||||
|
import { imageOptions, volumeOptions, createContainer } from '@/api/modules/container';
|
||||||
|
import { Container } from '@/api/interface/container';
|
||||||
|
|
||||||
const createVisiable = ref(false);
|
const createVisiable = ref(false);
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
image: '',
|
image: '',
|
||||||
command: '',
|
cmdStr: '',
|
||||||
|
cmd: [] as Array<string>,
|
||||||
publishAllPorts: false,
|
publishAllPorts: false,
|
||||||
ports: [],
|
exposedPorts: [] as Array<Container.Port>,
|
||||||
cpusetCpus: 1,
|
nanoCPUs: 1,
|
||||||
memeryLimit: 100,
|
memory: 100,
|
||||||
volumes: [],
|
memoryItem: 100,
|
||||||
|
memoryUnit: 'MB',
|
||||||
|
volumes: [] as Array<Container.Volume>,
|
||||||
autoRemove: false,
|
autoRemove: false,
|
||||||
labels: '',
|
labels: [] as Array<string>,
|
||||||
environment: '',
|
labelsStr: '',
|
||||||
restartPolicy: {
|
env: [] as Array<string>,
|
||||||
value: '',
|
envStr: '',
|
||||||
name: '',
|
restartPolicy: '',
|
||||||
maximumRetryCount: '',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const ports = ref();
|
const images = ref();
|
||||||
const volumes = ref();
|
const volumes = ref();
|
||||||
|
|
||||||
const acceptParams = (): void => {
|
const acceptParams = (): void => {
|
||||||
createVisiable.value = true;
|
createVisiable.value = true;
|
||||||
|
form.restartPolicy = 'no';
|
||||||
|
form.memoryUnit = 'MB';
|
||||||
|
loadImageOptions();
|
||||||
|
loadVolumeOptions();
|
||||||
};
|
};
|
||||||
|
|
||||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
name: [Rules.requiredInput, Rules.name],
|
name: [Rules.requiredInput, Rules.name],
|
||||||
type: [Rules.requiredSelect],
|
image: [Rules.requiredSelect],
|
||||||
specType: [Rules.requiredSelect],
|
nanoCPUs: [Rules.number],
|
||||||
week: [Rules.requiredSelect, Rules.number],
|
memoryItem: [Rules.number],
|
||||||
day: [Rules.number, { max: 31, min: 1 }],
|
|
||||||
hour: [Rules.number, { max: 23, min: 0 }],
|
|
||||||
minute: [Rules.number, { max: 60, min: 1 }],
|
|
||||||
|
|
||||||
script: [Rules.requiredInput],
|
|
||||||
website: [Rules.requiredSelect],
|
|
||||||
database: [Rules.requiredSelect],
|
|
||||||
url: [Rules.requiredInput],
|
|
||||||
sourceDir: [Rules.requiredSelect],
|
|
||||||
targetDirID: [Rules.requiredSelect, Rules.number],
|
|
||||||
retainCopies: [Rules.number],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormInstance = InstanceType<typeof ElForm>;
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
@ -160,39 +237,61 @@ const formRef = ref<FormInstance>();
|
|||||||
|
|
||||||
const handlePortsAdd = () => {
|
const handlePortsAdd = () => {
|
||||||
let item = {
|
let item = {
|
||||||
key: '',
|
containerPort: 80,
|
||||||
value: '',
|
hostPort: 8080,
|
||||||
};
|
};
|
||||||
ports.value.push(item);
|
form.exposedPorts.push(item);
|
||||||
};
|
};
|
||||||
const handlePortsDelete = (index: number) => {
|
const handlePortsDelete = (index: number) => {
|
||||||
ports.value.splice(index, 1);
|
form.exposedPorts.splice(index, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVolumesAdd = () => {
|
const handleVolumesAdd = () => {
|
||||||
let item = {
|
let item = {
|
||||||
from: '',
|
sourceDir: '',
|
||||||
bind: '',
|
containerDir: '',
|
||||||
mode: '',
|
mode: 'rw',
|
||||||
};
|
};
|
||||||
volumes.value.push(item);
|
form.volumes.push(item);
|
||||||
};
|
};
|
||||||
const handleVolumesDelete = (index: number) => {
|
const handleVolumesDelete = (index: number) => {
|
||||||
volumes.value.splice(index, 1);
|
form.volumes.splice(index, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
function restForm() {
|
const loadImageOptions = async () => {
|
||||||
if (formRef.value) {
|
const res = await imageOptions();
|
||||||
formRef.value.resetFields();
|
images.value = res.data;
|
||||||
}
|
};
|
||||||
}
|
const loadVolumeOptions = async () => {
|
||||||
|
const res = await volumeOptions();
|
||||||
|
volumes.value = res.data;
|
||||||
|
};
|
||||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
if (!formEl) return;
|
if (!formEl) return;
|
||||||
formEl.validate(async (valid) => {
|
formEl.validate(async (valid) => {
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
|
if (form.envStr.length !== 0) {
|
||||||
|
form.env = form.envStr.split('\n');
|
||||||
|
}
|
||||||
|
if (form.labelsStr.length !== 0) {
|
||||||
|
form.labels = form.labelsStr.split('\n');
|
||||||
|
}
|
||||||
|
if (form.cmdStr.length !== 0) {
|
||||||
|
form.cmd = form.cmdStr.split('\n');
|
||||||
|
}
|
||||||
|
switch (form.memoryUnit) {
|
||||||
|
case 'KB':
|
||||||
|
form.memory = form.memoryItem * 1024;
|
||||||
|
break;
|
||||||
|
case 'MB':
|
||||||
|
form.memory = form.memoryItem * 1024 * 1024;
|
||||||
|
break;
|
||||||
|
case 'GB':
|
||||||
|
form.memory = form.memoryItem * 1024 * 1024 * 1024;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await createContainer(form);
|
||||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
restForm();
|
|
||||||
emit('search');
|
emit('search');
|
||||||
createVisiable.value = false;
|
createVisiable.value = false;
|
||||||
});
|
});
|
||||||
|
@ -156,7 +156,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<CreateDialog ref="dialogCreateRef" />
|
<CreateDialog @search="search" ref="dialogCreateRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -8,12 +8,12 @@
|
|||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>{{ $t('container.importImage') }}</span>
|
<span>{{ $t('container.buildImage') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-form ref="formRef" :model="form" label-width="80px">
|
<el-form ref="formRef" :model="form" label-width="80px">
|
||||||
<el-form-item :label="$t('container.name')" :rules="Rules.requiredInput" prop="name">
|
<el-form-item :label="$t('container.name')" :rules="Rules.requiredInput" prop="name">
|
||||||
<el-input v-model="form.name" clearable />
|
<el-input :placeholder="$t('container.imageNameHelper')" v-model="form.name" clearable />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Dockerfile" :rules="Rules.requiredSelect" prop="from">
|
<el-form-item label="Dockerfile" :rules="Rules.requiredSelect" prop="from">
|
||||||
<el-radio-group v-model="form.from">
|
<el-radio-group v-model="form.from">
|
||||||
@ -32,7 +32,12 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('container.tag')">
|
<el-form-item :label="$t('container.tag')">
|
||||||
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" v-model="form.tagStr" />
|
<el-input
|
||||||
|
:placeholder="$t('container.tagHelper')"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
v-model="form.tagStr"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
@ -55,7 +60,7 @@
|
|||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="onSubmit(formRef)">{{ $t('container.import') }}</el-button>
|
<el-button @click="onSubmit(formRef)">{{ $t('container.build') }}</el-button>
|
||||||
<el-button @click="buildVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
<el-button @click="buildVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -18,7 +18,12 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('container.option')" prop="optionStr">
|
<el-form-item :label="$t('container.option')" prop="optionStr">
|
||||||
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" v-model="form.optionStr" />
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="$t('container.tagHelper')"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
v-model="form.optionStr"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('container.subnet')" prop="subnet">
|
<el-form-item :label="$t('container.subnet')" prop="subnet">
|
||||||
<el-input clearable v-model="form.subnet" />
|
<el-input clearable v-model="form.subnet" />
|
||||||
@ -30,7 +35,12 @@
|
|||||||
<el-input clearable v-model="form.scope" />
|
<el-input clearable v-model="form.scope" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('container.tag')" prop="labelStr">
|
<el-form-item :label="$t('container.tag')" prop="labelStr">
|
||||||
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" v-model="form.labelStr" />
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="$t('container.tagHelper')"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
v-model="form.labelStr"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
@ -15,10 +15,20 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('container.option')" prop="optionStr">
|
<el-form-item :label="$t('container.option')" prop="optionStr">
|
||||||
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" v-model="form.optionStr" />
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="$t('container.tagHelper')"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
v-model="form.optionStr"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('container.tag')" prop="labelStr">
|
<el-form-item :label="$t('container.tag')" prop="labelStr">
|
||||||
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" v-model="form.labelStr" />
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="$t('container.tagHelper')"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
v-model="form.labelStr"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
4
go.mod
4
go.mod
@ -8,6 +8,7 @@ require (
|
|||||||
github.com/compose-spec/compose-go v1.6.0
|
github.com/compose-spec/compose-go v1.6.0
|
||||||
github.com/dgraph-io/badger/v3 v3.2103.2
|
github.com/dgraph-io/badger/v3 v3.2103.2
|
||||||
github.com/docker/docker v20.10.18+incompatible
|
github.com/docker/docker v20.10.18+incompatible
|
||||||
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/fsnotify/fsnotify v1.5.4
|
github.com/fsnotify/fsnotify v1.5.4
|
||||||
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
|
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
|
||||||
github.com/gabriel-vasile/mimetype v1.4.1
|
github.com/gabriel-vasile/mimetype v1.4.1
|
||||||
@ -28,6 +29,7 @@ require (
|
|||||||
github.com/mojocn/base64Captcha v1.3.5
|
github.com/mojocn/base64Captcha v1.3.5
|
||||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.1.2
|
github.com/nicksnyder/go-i18n/v2 v2.1.2
|
||||||
|
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pkg/sftp v1.13.1
|
github.com/pkg/sftp v1.13.1
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
@ -66,7 +68,6 @@ require (
|
|||||||
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
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/dsnet/compress v0.0.1 // indirect
|
github.com/dsnet/compress v0.0.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
@ -118,7 +119,6 @@ require (
|
|||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // 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.3-0.20211202183452-c5a74bcca799 // indirect
|
|
||||||
github.com/opencontainers/runc v1.1.4 // 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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user