mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 05:31:44 +08:00
refactor: 使用api获取容器信息
This commit is contained in:
@@ -1,79 +1,78 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"net"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
"github.com/TheTNB/panel/pkg/types/docker/container"
|
||||
)
|
||||
|
||||
type containerRepo struct {
|
||||
cmd string
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewContainerRepo(cmd ...string) biz.ContainerRepo {
|
||||
if len(cmd) == 0 {
|
||||
cmd = append(cmd, "docker")
|
||||
func NewContainerRepo(sock ...string) biz.ContainerRepo {
|
||||
if len(sock) == 0 {
|
||||
sock = append(sock, "/var/run/docker.sock")
|
||||
}
|
||||
client := resty.New()
|
||||
client.SetTimeout(1 * time.Minute)
|
||||
client.SetRetryCount(2)
|
||||
client.SetTransport(&http.Transport{
|
||||
DialContext: func(ctx context.Context, _ string, _ string) (net.Conn, error) {
|
||||
return (&net.Dialer{}).DialContext(ctx, "unix", sock[0])
|
||||
},
|
||||
})
|
||||
client.SetBaseURL("http://d/v1.40")
|
||||
|
||||
return &containerRepo{
|
||||
cmd: cmd[0],
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// ListAll 列出所有容器
|
||||
func (r *containerRepo) ListAll() ([]types.Container, error) {
|
||||
output, err := shell.ExecfWithTimeout(10*time.Second, "%s ps -a --format json", r.cmd)
|
||||
var resp []container.Container
|
||||
_, err := r.client.R().SetResult(&resp).SetQueryParam("all", "true").Get("/containers/json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(output, "\n")
|
||||
|
||||
var containers []types.Container
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
continue
|
||||
for _, item := range resp {
|
||||
ports := make([]types.ContainerPort, 0)
|
||||
for _, port := range item.Ports {
|
||||
ports = append(ports, types.ContainerPort{
|
||||
ContainerStart: uint(port.PrivatePort),
|
||||
ContainerEnd: uint(port.PublicPort),
|
||||
HostStart: uint(port.PublicPort),
|
||||
HostEnd: uint(port.PublicPort),
|
||||
Protocol: port.Type,
|
||||
Host: port.IP,
|
||||
})
|
||||
}
|
||||
|
||||
var item struct {
|
||||
Command string `json:"Command"`
|
||||
CreatedAt string `json:"CreatedAt"`
|
||||
ID string `json:"ID"`
|
||||
Image string `json:"Image"`
|
||||
Labels string `json:"Labels"`
|
||||
LocalVolumes string `json:"LocalVolumes"`
|
||||
Mounts string `json:"Mounts"`
|
||||
Names string `json:"Names"`
|
||||
Networks string `json:"Networks"`
|
||||
Ports string `json:"Ports"`
|
||||
RunningFor string `json:"RunningFor"`
|
||||
Size string `json:"Size"`
|
||||
State string `json:"State"`
|
||||
Status string `json:"Status"`
|
||||
}
|
||||
if err = json.Unmarshal([]byte(line), &item); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal failed: %w", err)
|
||||
}
|
||||
|
||||
createdAt, _ := time.Parse("2006-01-02 15:04:05 -0700 MST", item.CreatedAt)
|
||||
containers = append(containers, types.Container{
|
||||
ID: item.ID,
|
||||
Name: item.Names,
|
||||
Name: item.Names[0],
|
||||
Image: item.Image,
|
||||
ImageID: item.ImageID,
|
||||
Command: item.Command,
|
||||
CreatedAt: createdAt,
|
||||
Ports: r.parsePorts(item.Ports),
|
||||
Labels: types.SliceToKV(strings.Split(item.Labels, ",")),
|
||||
CreatedAt: time.Unix(item.Created, 0),
|
||||
State: item.State,
|
||||
Status: item.Status,
|
||||
Ports: ports,
|
||||
Labels: types.MapToKV(item.Labels),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -97,10 +96,21 @@ func (r *containerRepo) ListByName(names string) ([]types.Container, error) {
|
||||
// Create 创建容器
|
||||
func (r *containerRepo) Create(req *request.ContainerCreate) (string, error) {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("%s create --name %s --image %s", r.cmd, req.Name, req.Image))
|
||||
|
||||
for _, port := range req.Ports {
|
||||
sb.WriteString(fmt.Sprintf(" -p %s:%d-%d:%d-%d/%s", port.Host, port.HostStart, port.HostEnd, port.ContainerStart, port.ContainerEnd, port.Protocol))
|
||||
sb.WriteString(fmt.Sprintf("docker run --name %s", req.Name))
|
||||
if req.PublishAllPorts {
|
||||
sb.WriteString(" -P")
|
||||
} else {
|
||||
for _, port := range req.Ports {
|
||||
sb.WriteString(" -p ")
|
||||
if port.Host != "" {
|
||||
sb.WriteString(fmt.Sprintf("%s:", port.Host))
|
||||
}
|
||||
if port.HostStart == port.HostEnd || port.ContainerStart == port.ContainerEnd {
|
||||
sb.WriteString(fmt.Sprintf("%d:%d/%s", port.HostStart, port.ContainerStart, port.Protocol))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%d-%d:%d-%d/%s", port.HostStart, port.HostEnd, port.ContainerStart, port.ContainerEnd, port.Protocol))
|
||||
}
|
||||
}
|
||||
}
|
||||
if req.Network != "" {
|
||||
sb.WriteString(fmt.Sprintf(" --network %s", req.Network))
|
||||
@@ -132,9 +142,6 @@ func (r *containerRepo) Create(req *request.ContainerCreate) (string, error) {
|
||||
if req.OpenStdin {
|
||||
sb.WriteString(" -i")
|
||||
}
|
||||
if req.PublishAllPorts {
|
||||
sb.WriteString(" -P")
|
||||
}
|
||||
if req.Tty {
|
||||
sb.WriteString(" -t")
|
||||
}
|
||||
@@ -148,94 +155,65 @@ func (r *containerRepo) Create(req *request.ContainerCreate) (string, error) {
|
||||
sb.WriteString(fmt.Sprintf(" --memory %d", req.Memory))
|
||||
}
|
||||
|
||||
return shell.ExecfWithTimeout(10*time.Second, sb.String()) // nolint: govet
|
||||
sb.WriteString(" %s")
|
||||
return shell.Execf(sb.String(), req.Image)
|
||||
}
|
||||
|
||||
// Remove 移除容器
|
||||
func (r *containerRepo) Remove(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s rm -f %s", r.cmd, id)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker rm -f %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Start 启动容器
|
||||
func (r *containerRepo) Start(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s start %s", r.cmd, id)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker start %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop 停止容器
|
||||
func (r *containerRepo) Stop(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s stop %s", r.cmd, id)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker stop %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Restart 重启容器
|
||||
func (r *containerRepo) Restart(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s restart %s", r.cmd, id)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker restart %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Pause 暂停容器
|
||||
func (r *containerRepo) Pause(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s pause %s", r.cmd, id)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker pause %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Unpause 恢复容器
|
||||
func (r *containerRepo) Unpause(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s unpause %s", r.cmd, id)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker unpause %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Kill 杀死容器
|
||||
func (r *containerRepo) Kill(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s kill %s", r.cmd, id)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker kill %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename 重命名容器
|
||||
func (r *containerRepo) Rename(id string, newName string) error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s rename %s %s", r.cmd, id, newName)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker rename %s %s", id, newName)
|
||||
return err
|
||||
}
|
||||
|
||||
// Logs 查看容器日志
|
||||
func (r *containerRepo) Logs(id string) (string, error) {
|
||||
return shell.ExecfWithTimeout(10*time.Second, "%s logs %s", r.cmd, id)
|
||||
return shell.ExecfWithTimeout(30*time.Second, "docker logs %s", id)
|
||||
}
|
||||
|
||||
// Prune 清理未使用的容器
|
||||
func (r *containerRepo) Prune() error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s container prune -f", r.cmd)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker container prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *containerRepo) parsePorts(ports string) []types.ContainerPort {
|
||||
var portList []types.ContainerPort
|
||||
|
||||
re := regexp.MustCompile(`(?P<host>[\d.:]+)?:(?P<public>\d+)->(?P<private>\d+)/(?P<protocol>\w+)`)
|
||||
|
||||
entries := strings.Split(ports, ", ") // 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp
|
||||
for _, entry := range entries {
|
||||
matches := re.FindStringSubmatch(entry)
|
||||
if len(matches) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
host := matches[1]
|
||||
public := matches[2]
|
||||
private := matches[3]
|
||||
protocol := matches[4]
|
||||
|
||||
portList = append(portList, types.ContainerPort{
|
||||
Host: host,
|
||||
HostStart: cast.ToUint(public),
|
||||
HostEnd: cast.ToUint(public),
|
||||
ContainerStart: cast.ToUint(private),
|
||||
ContainerEnd: cast.ToUint(private),
|
||||
Protocol: protocol,
|
||||
})
|
||||
}
|
||||
|
||||
return portList
|
||||
}
|
||||
|
||||
@@ -1,68 +1,64 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
"github.com/TheTNB/panel/pkg/types/docker/image"
|
||||
)
|
||||
|
||||
type containerImageRepo struct {
|
||||
cmd string
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewContainerImageRepo(cmd ...string) biz.ContainerImageRepo {
|
||||
if len(cmd) == 0 {
|
||||
cmd = append(cmd, "docker")
|
||||
func NewContainerImageRepo(sock ...string) biz.ContainerImageRepo {
|
||||
if len(sock) == 0 {
|
||||
sock = append(sock, "/var/run/docker.sock")
|
||||
}
|
||||
client := resty.New()
|
||||
client.SetTimeout(1 * time.Minute)
|
||||
client.SetRetryCount(2)
|
||||
client.SetTransport(&http.Transport{
|
||||
DialContext: func(ctx context.Context, _ string, _ string) (net.Conn, error) {
|
||||
return (&net.Dialer{}).DialContext(ctx, "unix", sock[0])
|
||||
},
|
||||
})
|
||||
client.SetBaseURL("http://d/v1.40")
|
||||
|
||||
return &containerImageRepo{
|
||||
cmd: cmd[0],
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// List 列出镜像
|
||||
func (r *containerImageRepo) List() ([]types.ContainerImage, error) {
|
||||
output, err := shell.ExecfWithTimeout(10*time.Second, "%s images -a --format json", r.cmd)
|
||||
var resp []image.Image
|
||||
_, err := r.client.R().SetResult(&resp).SetQueryParam("all", "true").Get("/images/json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(output, "\n")
|
||||
|
||||
var images []types.ContainerImage
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var item struct {
|
||||
ID string `json:"ID"`
|
||||
Containers string `json:"Containers"`
|
||||
Repository string `json:"Repository"`
|
||||
Tag string `json:"Tag"`
|
||||
Digest string `json:"Digest"`
|
||||
CreatedAt string `json:"CreatedAt"`
|
||||
Size string `json:"Size"`
|
||||
SharedSize string `json:"SharedSize"`
|
||||
VirtualSize string `json:"VirtualSize"`
|
||||
}
|
||||
if err = json.Unmarshal([]byte(line), &item); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal failed: %w", err)
|
||||
}
|
||||
|
||||
createdAt, _ := time.Parse("2006-01-02 15:04:05 -0700 MST", item.CreatedAt)
|
||||
for _, item := range resp {
|
||||
images = append(images, types.ContainerImage{
|
||||
ID: item.ID,
|
||||
Containers: cast.ToInt64(item.Containers),
|
||||
Tag: item.Tag,
|
||||
CreatedAt: createdAt,
|
||||
Size: item.Size,
|
||||
ID: item.ID,
|
||||
Containers: item.Containers,
|
||||
RepoTags: item.RepoTags,
|
||||
RepoDigests: item.RepoDigests,
|
||||
Size: str.FormatBytes(float64(item.Size)),
|
||||
Labels: types.MapToKV(item.Labels),
|
||||
CreatedAt: time.Unix(item.Created, 0),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -74,16 +70,16 @@ func (r *containerImageRepo) Pull(req *request.ContainerImagePull) error {
|
||||
var sb strings.Builder
|
||||
|
||||
if req.Auth {
|
||||
sb.WriteString(fmt.Sprintf("%s login -u %s -p %s", r.cmd, req.Username, req.Password))
|
||||
if _, err := shell.ExecfWithTimeout(1*time.Minute, sb.String()); err != nil {
|
||||
sb.WriteString(fmt.Sprintf("docker login -u %s -p %s", req.Username, req.Password))
|
||||
if _, err := shell.ExecfWithTimeout(1*time.Minute, sb.String()); err != nil { // nolint: govet
|
||||
return fmt.Errorf("login failed: %w", err)
|
||||
}
|
||||
sb.Reset()
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("%s pull %s", r.cmd, req.Name))
|
||||
sb.WriteString(fmt.Sprintf("docker pull %s", req.Name))
|
||||
|
||||
if _, err := shell.ExecfWithTimeout(20*time.Minute, sb.String()); err != nil { // nolint: govet
|
||||
if _, err := shell.Execf(sb.String()); err != nil { // nolint: govet
|
||||
return fmt.Errorf("pull failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -92,12 +88,12 @@ func (r *containerImageRepo) Pull(req *request.ContainerImagePull) error {
|
||||
|
||||
// Remove 删除镜像
|
||||
func (r *containerImageRepo) Remove(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "%s rmi %s", r.cmd, id)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker rmi %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune 清理未使用的镜像
|
||||
func (r *containerImageRepo) Prune() error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "%s image prune -f", r.cmd)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker image prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,97 +1,57 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
"github.com/TheTNB/panel/pkg/types/docker/network"
|
||||
)
|
||||
|
||||
type containerNetworkRepo struct {
|
||||
cmd string
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewContainerNetworkRepo(cmd ...string) biz.ContainerNetworkRepo {
|
||||
if len(cmd) == 0 {
|
||||
cmd = append(cmd, "docker")
|
||||
func NewContainerNetworkRepo(sock ...string) biz.ContainerNetworkRepo {
|
||||
if len(sock) == 0 {
|
||||
sock = append(sock, "/var/run/docker.sock")
|
||||
}
|
||||
client := resty.New()
|
||||
client.SetTimeout(1 * time.Minute)
|
||||
client.SetRetryCount(2)
|
||||
client.SetTransport(&http.Transport{
|
||||
DialContext: func(ctx context.Context, _ string, _ string) (net.Conn, error) {
|
||||
return (&net.Dialer{}).DialContext(ctx, "unix", sock[0])
|
||||
},
|
||||
})
|
||||
client.SetBaseURL("http://d/v1.40")
|
||||
|
||||
return &containerNetworkRepo{
|
||||
cmd: cmd[0],
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// List 列出网络
|
||||
func (r *containerNetworkRepo) List() ([]types.ContainerNetwork, error) {
|
||||
output, err := shell.ExecfWithTimeout(10*time.Second, "%s network ls --format json", r.cmd)
|
||||
var resp []network.Network
|
||||
_, err := r.client.R().SetResult(&resp).Get("/networks")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(output, "\n")
|
||||
|
||||
var networks []types.ContainerNetwork
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var item struct {
|
||||
CreatedAt string `json:"CreatedAt"`
|
||||
Driver string `json:"Driver"`
|
||||
ID string `json:"ID"`
|
||||
IPv6 string `json:"IPv6"`
|
||||
Internal string `json:"Internal"`
|
||||
Labels string `json:"Labels"`
|
||||
Name string `json:"Name"`
|
||||
Scope string `json:"Scope"`
|
||||
}
|
||||
if err = json.Unmarshal([]byte(line), &item); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal failed: %w", err)
|
||||
}
|
||||
|
||||
output, err = shell.ExecfWithTimeout(10*time.Second, "%s network inspect %s", r.cmd, item.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("inspect failed: %w", err)
|
||||
}
|
||||
var inspect []struct {
|
||||
Name string `json:"Name"`
|
||||
Id string `json:"Id"`
|
||||
Created time.Time `json:"Created"`
|
||||
Scope string `json:"Scope"`
|
||||
Driver string `json:"Driver"`
|
||||
EnableIPv6 bool `json:"EnableIPv6"`
|
||||
IPAM struct {
|
||||
Driver string `json:"Driver"`
|
||||
Options map[string]string `json:"Options"`
|
||||
Config []struct {
|
||||
Subnet string `json:"Subnet"`
|
||||
IPRange string `json:"IPRange"`
|
||||
Gateway string `json:"Gateway"`
|
||||
AuxAddress map[string]string `json:"AuxiliaryAddresses"`
|
||||
} `json:"Config"`
|
||||
} `json:"IPAM"`
|
||||
Internal bool `json:"Internal"`
|
||||
Attachable bool `json:"Attachable"`
|
||||
Ingress bool `json:"Ingress"`
|
||||
ConfigOnly bool `json:"ConfigOnly"`
|
||||
Options map[string]string `json:"Options"`
|
||||
Labels map[string]string `json:"Labels"`
|
||||
}
|
||||
if err = json.Unmarshal([]byte(output), &inspect); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal inspect failed: %w", err)
|
||||
}
|
||||
if len(inspect) == 0 {
|
||||
return nil, fmt.Errorf("inspect empty")
|
||||
}
|
||||
|
||||
var ipamConfigs []types.ContainerNetworkIPAMConfig
|
||||
for _, ipam := range inspect[0].IPAM.Config {
|
||||
for _, item := range resp {
|
||||
ipamConfigs := make([]types.ContainerNetworkIPAMConfig, 0)
|
||||
for _, ipam := range item.IPAM.Config {
|
||||
ipamConfigs = append(ipamConfigs, types.ContainerNetworkIPAMConfig{
|
||||
Subnet: ipam.Subnet,
|
||||
IPRange: ipam.IPRange,
|
||||
@@ -99,25 +59,23 @@ func (r *containerNetworkRepo) List() ([]types.ContainerNetwork, error) {
|
||||
AuxAddress: ipam.AuxAddress,
|
||||
})
|
||||
}
|
||||
|
||||
createdAt, _ := time.Parse("2006-01-02 15:04:05 -0700 MST", item.CreatedAt)
|
||||
networks = append(networks, types.ContainerNetwork{
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
Driver: item.Driver,
|
||||
IPv6: cast.ToBool(item.IPv6),
|
||||
Internal: cast.ToBool(item.Internal),
|
||||
Attachable: cast.ToBool(inspect[0].Attachable),
|
||||
Ingress: cast.ToBool(inspect[0].Ingress),
|
||||
IPv6: item.EnableIPv6,
|
||||
Internal: item.Internal,
|
||||
Attachable: item.Attachable,
|
||||
Ingress: item.Ingress,
|
||||
Scope: item.Scope,
|
||||
CreatedAt: createdAt,
|
||||
CreatedAt: item.Created,
|
||||
IPAM: types.ContainerNetworkIPAM{
|
||||
Driver: inspect[0].IPAM.Driver,
|
||||
Options: types.MapToKV(inspect[0].IPAM.Options),
|
||||
Driver: item.IPAM.Driver,
|
||||
Options: types.MapToKV(item.IPAM.Options),
|
||||
Config: ipamConfigs,
|
||||
},
|
||||
Options: types.MapToKV(inspect[0].Options),
|
||||
Labels: types.SliceToKV(strings.Split(item.Labels, ",")),
|
||||
Options: types.MapToKV(item.Options),
|
||||
Labels: types.MapToKV(item.Labels),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -128,7 +86,7 @@ func (r *containerNetworkRepo) List() ([]types.ContainerNetwork, error) {
|
||||
func (r *containerNetworkRepo) Create(req *request.ContainerNetworkCreate) (string, error) {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(fmt.Sprintf("%s network create --driver %s", r.cmd, req.Driver))
|
||||
sb.WriteString(fmt.Sprintf("docker network create --driver %s", req.Driver))
|
||||
sb.WriteString(fmt.Sprintf(" %s", req.Name))
|
||||
|
||||
if req.Ipv4.Enabled {
|
||||
@@ -152,17 +110,17 @@ func (r *containerNetworkRepo) Create(req *request.ContainerNetworkCreate) (stri
|
||||
sb.WriteString(fmt.Sprintf(" --opt %s=%s", option.Key, option.Value))
|
||||
}
|
||||
|
||||
return shell.ExecfWithTimeout(10*time.Second, "%s", sb.String()) // nolint: govet
|
||||
return shell.ExecfWithTimeout(30*time.Second, sb.String()) // nolint: govet
|
||||
}
|
||||
|
||||
// Remove 删除网络
|
||||
func (r *containerNetworkRepo) Remove(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s network rm -f %s", r.cmd, id)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker network rm -f %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune 清理未使用的网络
|
||||
func (r *containerNetworkRepo) Prune() error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s network prune -f", r.cmd)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker network prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,88 +1,66 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/str"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
"github.com/TheTNB/panel/pkg/types/docker/volume"
|
||||
)
|
||||
|
||||
type containerVolumeRepo struct {
|
||||
cmd string
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewContainerVolumeRepo(cmd ...string) biz.ContainerVolumeRepo {
|
||||
if len(cmd) == 0 {
|
||||
cmd = append(cmd, "docker")
|
||||
func NewContainerVolumeRepo(sock ...string) biz.ContainerVolumeRepo {
|
||||
if len(sock) == 0 {
|
||||
sock = append(sock, "/var/run/docker.sock")
|
||||
}
|
||||
client := resty.New()
|
||||
client.SetTimeout(1 * time.Minute)
|
||||
client.SetRetryCount(2)
|
||||
client.SetTransport(&http.Transport{
|
||||
DialContext: func(ctx context.Context, _ string, _ string) (net.Conn, error) {
|
||||
return (&net.Dialer{}).DialContext(ctx, "unix", sock[0])
|
||||
},
|
||||
})
|
||||
client.SetBaseURL("http://d/v1.40")
|
||||
|
||||
return &containerVolumeRepo{
|
||||
cmd: cmd[0],
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// List 列出存储卷
|
||||
func (r *containerVolumeRepo) List() ([]types.ContainerVolume, error) {
|
||||
output, err := shell.ExecfWithTimeout(10*time.Second, "%s volume ls --format json", r.cmd)
|
||||
var resp volume.ListResponse
|
||||
_, err := r.client.R().SetResult(&resp).Get("/volumes")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(output, "\n")
|
||||
|
||||
var volumes []types.ContainerVolume
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var item struct {
|
||||
Availability string `json:"Availability"`
|
||||
Driver string `json:"Driver"`
|
||||
Group string `json:"Group"`
|
||||
Labels string `json:"Labels"`
|
||||
Links string `json:"Links"`
|
||||
Mountpoint string `json:"Mountpoint"`
|
||||
Name string `json:"Name"`
|
||||
Scope string `json:"Scope"`
|
||||
Size string `json:"Size"`
|
||||
Status string `json:"Status"`
|
||||
}
|
||||
if err = json.Unmarshal([]byte(line), &item); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal failed: %w", err)
|
||||
}
|
||||
|
||||
output, err = shell.ExecfWithTimeout(10*time.Second, "%s volume inspect %s", r.cmd, item.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("inspect failed: %w", err)
|
||||
}
|
||||
var inspect []struct {
|
||||
CreatedAt time.Time `json:"CreatedAt"`
|
||||
Driver string `json:"Driver"`
|
||||
Labels map[string]string `json:"Labels"`
|
||||
Mountpoint string `json:"Mountpoint"`
|
||||
Name string `json:"Name"`
|
||||
Options map[string]string `json:"Options"`
|
||||
Scope string `json:"Scope"`
|
||||
}
|
||||
if err = json.Unmarshal([]byte(output), &inspect); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal inspect failed: %w", err)
|
||||
}
|
||||
if len(inspect) == 0 {
|
||||
return nil, fmt.Errorf("inspect empty")
|
||||
}
|
||||
|
||||
for _, item := range resp.Volumes {
|
||||
volumes = append(volumes, types.ContainerVolume{
|
||||
Name: item.Name,
|
||||
Driver: item.Driver,
|
||||
Scope: item.Scope,
|
||||
MountPoint: item.Mountpoint,
|
||||
CreatedAt: inspect[0].CreatedAt,
|
||||
Options: types.MapToKV(inspect[0].Options),
|
||||
Labels: types.SliceToKV(strings.Split(item.Labels, ",")),
|
||||
CreatedAt: item.CreatedAt,
|
||||
Labels: types.MapToKV(item.Labels),
|
||||
Options: types.MapToKV(item.Options),
|
||||
RefCount: item.UsageData.RefCount,
|
||||
Size: str.FormatBytes(float64(item.UsageData.Size)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -91,17 +69,32 @@ func (r *containerVolumeRepo) List() ([]types.ContainerVolume, error) {
|
||||
|
||||
// Create 创建存储卷
|
||||
func (r *containerVolumeRepo) Create(req *request.ContainerVolumeCreate) (string, error) {
|
||||
return "", nil
|
||||
var sb strings.Builder
|
||||
sb.WriteString("docker volume create")
|
||||
sb.WriteString(fmt.Sprintf(" %s", req.Name))
|
||||
|
||||
if req.Driver != "" {
|
||||
sb.WriteString(fmt.Sprintf(" --driver %s", req.Driver))
|
||||
}
|
||||
for _, label := range req.Labels {
|
||||
sb.WriteString(fmt.Sprintf(" --label %s=%s", label.Key, label.Value))
|
||||
}
|
||||
|
||||
for _, option := range req.Options {
|
||||
sb.WriteString(fmt.Sprintf(" --opt %s=%s", option.Key, option.Value))
|
||||
}
|
||||
|
||||
return shell.ExecfWithTimeout(30*time.Second, sb.String()) // nolint: govet
|
||||
}
|
||||
|
||||
// Remove 删除存储卷
|
||||
func (r *containerVolumeRepo) Remove(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s volume rm -f %s", r.cmd, id)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker volume rm -f %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune 清理未使用的存储卷
|
||||
func (r *containerVolumeRepo) Prune() error {
|
||||
_, err := shell.ExecfWithTimeout(10*time.Second, "%s volume prune -f", r.cmd)
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker volume prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/go-rat/chix"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/data"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
|
||||
@@ -34,7 +34,7 @@ func KVToMap(kvs []KV) map[string]string {
|
||||
|
||||
// MapToKV 将 map 转换为 key-value 切片
|
||||
func MapToKV(m map[string]string) []KV {
|
||||
var kvs []KV
|
||||
kvs := make([]KV, 0)
|
||||
for k, v := range m {
|
||||
kvs = append(kvs, KV{Key: k, Value: v})
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func MapToKV(m map[string]string) []KV {
|
||||
|
||||
// KVToSlice 将 key-value 切片转换为 key=value 切片
|
||||
func KVToSlice(kvs []KV) []string {
|
||||
var s []string
|
||||
s := make([]string, 0)
|
||||
for _, item := range kvs {
|
||||
s = append(s, item.Key+"="+item.Value)
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func KVToSlice(kvs []KV) []string {
|
||||
|
||||
// SliceToKV 将 key=value 切片转换为 key-value 切片
|
||||
func SliceToKV(s []string) []KV {
|
||||
var kvs []KV
|
||||
kvs := make([]KV, 0)
|
||||
for _, item := range s {
|
||||
kv := strings.SplitN(item, "=", 2)
|
||||
if len(kv) == 2 {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
ID string `json:"id"`
|
||||
@@ -8,11 +10,11 @@ type Container struct {
|
||||
Image string `json:"image"`
|
||||
ImageID string `json:"image_id"`
|
||||
Command string `json:"command"`
|
||||
State string `json:"state"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Ports []ContainerPort `json:"ports"`
|
||||
Labels []KV
|
||||
State string
|
||||
Status string
|
||||
Labels []KV `json:"labels"`
|
||||
}
|
||||
|
||||
type ContainerPort struct {
|
||||
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
)
|
||||
|
||||
type ContainerImage struct {
|
||||
ID string `json:"id"`
|
||||
Containers int64 `json:"containers"`
|
||||
Tag string `json:"tag"`
|
||||
Size string `json:"size"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ID string `json:"id"`
|
||||
Containers int64 `json:"containers"`
|
||||
RepoTags []string `json:"repo_tags"`
|
||||
RepoDigests []string `json:"repo_digests"`
|
||||
Size string `json:"size"`
|
||||
Labels []KV `json:"labels"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ type ContainerNetwork struct {
|
||||
|
||||
// ContainerNetworkIPAM represents IP Address Management
|
||||
type ContainerNetworkIPAM struct {
|
||||
Driver string
|
||||
Options []KV
|
||||
Config []ContainerNetworkIPAMConfig
|
||||
Driver string `json:"driver"`
|
||||
Options []KV `json:"options"`
|
||||
Config []ContainerNetworkIPAMConfig `json:"config"`
|
||||
}
|
||||
|
||||
// ContainerNetworkIPAMConfig represents IPAM configurations
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ContainerVolume struct {
|
||||
Name string `json:"name"`
|
||||
@@ -10,4 +12,6 @@ type ContainerVolume struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Labels []KV `json:"labels"`
|
||||
Options []KV `json:"options"`
|
||||
RefCount int64 `json:"ref_count"`
|
||||
Size string `json:"size"`
|
||||
}
|
||||
|
||||
71
pkg/types/docker/container/container.go
Normal file
71
pkg/types/docker/container/container.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"github.com/TheTNB/panel/pkg/types/docker"
|
||||
)
|
||||
|
||||
// Container contains response of Engine API:
|
||||
// GET "/containers/json"
|
||||
type Container struct {
|
||||
ID string `json:"Id"`
|
||||
Names []string
|
||||
Image string
|
||||
ImageID string
|
||||
Command string
|
||||
Created int64
|
||||
Ports []docker.Port
|
||||
SizeRw int64 `json:",omitempty"`
|
||||
SizeRootFs int64 `json:",omitempty"`
|
||||
Labels map[string]string
|
||||
State string
|
||||
Status string
|
||||
HostConfig struct {
|
||||
NetworkMode string `json:",omitempty"`
|
||||
Annotations map[string]string `json:",omitempty"`
|
||||
}
|
||||
Mounts []MountPoint
|
||||
}
|
||||
|
||||
// MountPoint represents a mount point configuration inside the container.
|
||||
// This is used for reporting the mountpoints in use by a container.
|
||||
type MountPoint struct {
|
||||
// Type is the type of mount, see `Type<foo>` definitions in
|
||||
// github.com/docker/docker/api/types/mount.Type
|
||||
Type string `json:",omitempty"`
|
||||
|
||||
// Name is the name reference to the underlying data defined by `Source`
|
||||
// e.g., the volume name.
|
||||
Name string `json:",omitempty"`
|
||||
|
||||
// Source is the source location of the mount.
|
||||
//
|
||||
// For volumes, this contains the storage location of the volume (within
|
||||
// `/var/lib/docker/volumes/`). For bind-mounts, and `npipe`, this contains
|
||||
// the source (host) part of the bind-mount. For `tmpfs` mount points, this
|
||||
// field is empty.
|
||||
Source string
|
||||
|
||||
// Destination is the path relative to the container root (`/`) where the
|
||||
// Source is mounted inside the container.
|
||||
Destination string
|
||||
|
||||
// Driver is the volume driver used to create the volume (if it is a volume).
|
||||
Driver string `json:",omitempty"`
|
||||
|
||||
// Mode is a comma separated list of options supplied by the user when
|
||||
// creating the bind/volume mount.
|
||||
//
|
||||
// The default is platform-specific (`"z"` on Linux, empty on Windows).
|
||||
Mode string
|
||||
|
||||
// RW indicates whether the mount is mounted writable (read-write).
|
||||
RW bool
|
||||
|
||||
// Propagation describes how mounts are propagated from the host into the
|
||||
// mount point, and vice-versa. Refer to the Linux kernel documentation
|
||||
// for details:
|
||||
// https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
|
||||
//
|
||||
// This field is not used on Windows.
|
||||
Propagation string
|
||||
}
|
||||
80
pkg/types/docker/image/image.go
Normal file
80
pkg/types/docker/image/image.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package image
|
||||
|
||||
// Image summary
|
||||
type Image struct {
|
||||
|
||||
// Number of containers using this image. Includes both stopped and running
|
||||
// containers.
|
||||
//
|
||||
// This size is not calculated by default, and depends on which API endpoint
|
||||
// is used. `-1` indicates that the value has not been set / calculated.
|
||||
//
|
||||
// Required: true
|
||||
Containers int64 `json:"Containers"`
|
||||
|
||||
// Date and time at which the image was created as a Unix timestamp
|
||||
// (number of seconds sinds EPOCH).
|
||||
//
|
||||
// Required: true
|
||||
Created int64 `json:"Created"`
|
||||
|
||||
// ID is the content-addressable ID of an image.
|
||||
//
|
||||
// This identifier is a content-addressable digest calculated from the
|
||||
// image's configuration (which includes the digests of layers used by
|
||||
// the image).
|
||||
//
|
||||
// Note that this digest differs from the `RepoDigests` below, which
|
||||
// holds digests of image manifests that reference the image.
|
||||
//
|
||||
// Required: true
|
||||
ID string `json:"Id"`
|
||||
|
||||
// User-defined key/value metadata.
|
||||
// Required: true
|
||||
Labels map[string]string `json:"Labels"`
|
||||
|
||||
// ID of the parent image.
|
||||
//
|
||||
// Depending on how the image was created, this field may be empty and
|
||||
// is only set for images that were built/created locally. This field
|
||||
// is empty if the image was pulled from an image registry.
|
||||
//
|
||||
// Required: true
|
||||
ParentID string `json:"ParentId"`
|
||||
|
||||
// List of content-addressable digests of locally available image manifests
|
||||
// that the image is referenced from. Multiple manifests can refer to the
|
||||
// same image.
|
||||
//
|
||||
// These digests are usually only available if the image was either pulled
|
||||
// from a registry, or if the image was pushed to a registry, which is when
|
||||
// the manifest is generated and its digest calculated.
|
||||
//
|
||||
// Required: true
|
||||
RepoDigests []string `json:"RepoDigests"`
|
||||
|
||||
// List of image names/tags in the local image cache that reference this
|
||||
// image.
|
||||
//
|
||||
// Multiple image tags can refer to the same image, and this list may be
|
||||
// empty if no tags reference the image, in which case the image is
|
||||
// "untagged", in which case it can still be referenced by its ID.
|
||||
//
|
||||
// Required: true
|
||||
RepoTags []string `json:"RepoTags"`
|
||||
|
||||
// Total size of image layers that are shared between this image and other
|
||||
// images.
|
||||
//
|
||||
// This size is not calculated by default. `-1` indicates that the value
|
||||
// has not been set / calculated.
|
||||
//
|
||||
// Required: true
|
||||
SharedSize int64 `json:"SharedSize"`
|
||||
|
||||
// Total size of the image including all layers it is composed of.
|
||||
//
|
||||
// Required: true
|
||||
Size int64 `json:"Size"`
|
||||
}
|
||||
79
pkg/types/docker/network/endpoint.go
Normal file
79
pkg/types/docker/network/endpoint.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// EndpointSettings stores the network endpoint details
|
||||
type EndpointSettings struct {
|
||||
// Configurations
|
||||
IPAMConfig *EndpointIPAMConfig
|
||||
Links []string
|
||||
Aliases []string // Aliases holds the list of extra, user-specified DNS names for this endpoint.
|
||||
// MacAddress may be used to specify a MAC address when the container is created.
|
||||
// Once the container is running, it becomes operational data (it may contain a
|
||||
// generated address).
|
||||
MacAddress string
|
||||
DriverOpts map[string]string
|
||||
// Operational data
|
||||
NetworkID string
|
||||
EndpointID string
|
||||
Gateway string
|
||||
IPAddress string
|
||||
IPPrefixLen int
|
||||
IPv6Gateway string
|
||||
GlobalIPv6Address string
|
||||
GlobalIPv6PrefixLen int
|
||||
// DNSNames holds all the (non fully qualified) DNS names associated to this endpoint. First entry is used to
|
||||
// generate PTR records.
|
||||
DNSNames []string
|
||||
}
|
||||
|
||||
// Copy makes a deep copy of `EndpointSettings`
|
||||
func (es *EndpointSettings) Copy() *EndpointSettings {
|
||||
epCopy := *es
|
||||
if es.IPAMConfig != nil {
|
||||
epCopy.IPAMConfig = es.IPAMConfig.Copy()
|
||||
}
|
||||
|
||||
if es.Links != nil {
|
||||
links := make([]string, 0, len(es.Links))
|
||||
epCopy.Links = append(links, es.Links...)
|
||||
}
|
||||
|
||||
if es.Aliases != nil {
|
||||
aliases := make([]string, 0, len(es.Aliases))
|
||||
epCopy.Aliases = append(aliases, es.Aliases...)
|
||||
}
|
||||
|
||||
if len(es.DNSNames) > 0 {
|
||||
epCopy.DNSNames = make([]string, len(es.DNSNames))
|
||||
copy(epCopy.DNSNames, es.DNSNames)
|
||||
}
|
||||
|
||||
return &epCopy
|
||||
}
|
||||
|
||||
// EndpointIPAMConfig represents IPAM configurations for the endpoint
|
||||
type EndpointIPAMConfig struct {
|
||||
IPv4Address string `json:",omitempty"`
|
||||
IPv6Address string `json:",omitempty"`
|
||||
LinkLocalIPs []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Copy makes a copy of the endpoint ipam config
|
||||
func (cfg *EndpointIPAMConfig) Copy() *EndpointIPAMConfig {
|
||||
cfgCopy := *cfg
|
||||
cfgCopy.LinkLocalIPs = make([]string, 0, len(cfg.LinkLocalIPs))
|
||||
cfgCopy.LinkLocalIPs = append(cfgCopy.LinkLocalIPs, cfg.LinkLocalIPs...)
|
||||
return &cfgCopy
|
||||
}
|
||||
|
||||
// NetworkSubnet describes a user-defined subnet for a specific network. It's only used to validate if an
|
||||
// EndpointIPAMConfig is valid for a specific network.
|
||||
type NetworkSubnet interface {
|
||||
// Contains checks whether the NetworkSubnet contains [addr].
|
||||
Contains(addr net.IP) bool
|
||||
// IsStatic checks whether the subnet was statically allocated (ie. user-defined).
|
||||
IsStatic() bool
|
||||
}
|
||||
23
pkg/types/docker/network/ipam.go
Normal file
23
pkg/types/docker/network/ipam.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package network
|
||||
|
||||
// IPAM represents IP Address Management
|
||||
type IPAM struct {
|
||||
Driver string
|
||||
Options map[string]string // Per network IPAM driver options
|
||||
Config []IPAMConfig
|
||||
}
|
||||
|
||||
// IPAMConfig represents IPAM configurations
|
||||
type IPAMConfig struct {
|
||||
Subnet string `json:",omitempty"`
|
||||
IPRange string `json:",omitempty"`
|
||||
Gateway string `json:",omitempty"`
|
||||
AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"`
|
||||
}
|
||||
|
||||
type ipFamily string
|
||||
|
||||
const (
|
||||
ip4 ipFamily = "IPv4"
|
||||
ip6 ipFamily = "IPv6"
|
||||
)
|
||||
63
pkg/types/docker/network/network.go
Normal file
63
pkg/types/docker/network/network.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package network // import "github.com/docker/docker/api/types/network"
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Network is the body of the "get network" http response message.
|
||||
type Network struct {
|
||||
Name string // Name is the name of the network
|
||||
ID string `json:"Id"` // ID uniquely identifies a network on a single machine
|
||||
Created time.Time // Created is the time the network created
|
||||
Scope string // Scope describes the level at which the network exists (e.g. `swarm` for cluster-wide or `local` for machine level)
|
||||
Driver string // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`)
|
||||
EnableIPv6 bool // EnableIPv6 represents whether to enable IPv6
|
||||
IPAM IPAM // IPAM is the network's IP Address Management
|
||||
Internal bool // Internal represents if the network is used internal only
|
||||
Attachable bool // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode.
|
||||
Ingress bool // Ingress indicates the network is providing the routing-mesh for the swarm cluster.
|
||||
ConfigFrom ConfigReference // ConfigFrom specifies the source which will provide the configuration for this network.
|
||||
ConfigOnly bool // ConfigOnly networks are place-holder networks for network configurations to be used by other networks. ConfigOnly networks cannot be used directly to run containers or services.
|
||||
Containers map[string]EndpointResource // Containers contains endpoints belonging to the network
|
||||
Options map[string]string // Options holds the network specific options to use for when creating the network
|
||||
Labels map[string]string // Labels holds metadata specific to the network being created
|
||||
Peers []PeerInfo `json:",omitempty"` // List of peer nodes for an overlay network
|
||||
Services map[string]ServiceInfo `json:",omitempty"`
|
||||
}
|
||||
|
||||
// PeerInfo represents one peer of an overlay network
|
||||
type PeerInfo struct {
|
||||
Name string
|
||||
IP string
|
||||
}
|
||||
|
||||
// Task carries the information about one backend task
|
||||
type Task struct {
|
||||
Name string
|
||||
EndpointID string
|
||||
EndpointIP string
|
||||
Info map[string]string
|
||||
}
|
||||
|
||||
// ServiceInfo represents service parameters with the list of service's tasks
|
||||
type ServiceInfo struct {
|
||||
VIP string
|
||||
Ports []string
|
||||
LocalLBIndex int
|
||||
Tasks []Task
|
||||
}
|
||||
|
||||
// EndpointResource contains network resources allocated and used for a
|
||||
// container in a network.
|
||||
type EndpointResource struct {
|
||||
Name string
|
||||
EndpointID string
|
||||
MacAddress string
|
||||
IPv4Address string
|
||||
IPv6Address string
|
||||
}
|
||||
|
||||
// ConfigReference specifies the source which provides a network's configuration
|
||||
type ConfigReference struct {
|
||||
Network string
|
||||
}
|
||||
19
pkg/types/docker/port.go
Normal file
19
pkg/types/docker/port.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package docker
|
||||
|
||||
// Port An open port on a container
|
||||
type Port struct {
|
||||
|
||||
// Host IP address that the container's port is mapped to
|
||||
IP string `json:"IP,omitempty"`
|
||||
|
||||
// Port on the container
|
||||
// Required: true
|
||||
PrivatePort uint16 `json:"PrivatePort"`
|
||||
|
||||
// Port exposed on the host
|
||||
PublicPort uint16 `json:"PublicPort,omitempty"`
|
||||
|
||||
// type
|
||||
// Required: true
|
||||
Type string `json:"Type"`
|
||||
}
|
||||
23
pkg/types/docker/swarm/meta.go
Normal file
23
pkg/types/docker/swarm/meta.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Version represents the internal object version.
|
||||
type Version struct {
|
||||
Index uint64 `json:",omitempty"`
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer interface.
|
||||
func (v Version) String() string {
|
||||
return strconv.FormatUint(v.Index, 10)
|
||||
}
|
||||
|
||||
// Meta is a base object inherited by most of the other once.
|
||||
type Meta struct {
|
||||
Version Version `json:",omitempty"`
|
||||
CreatedAt time.Time `json:",omitempty"`
|
||||
UpdatedAt time.Time `json:",omitempty"`
|
||||
}
|
||||
18
pkg/types/docker/volume/list_response.go
Normal file
18
pkg/types/docker/volume/list_response.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package volume
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// ListResponse VolumeListResponse
|
||||
//
|
||||
// Volume list response
|
||||
// swagger:model ListResponse
|
||||
type ListResponse struct {
|
||||
|
||||
// List of volumes
|
||||
Volumes []*Volume `json:"Volumes"`
|
||||
|
||||
// Warnings that occurred when fetching the list of volumes.
|
||||
//
|
||||
Warnings []string `json:"Warnings"`
|
||||
}
|
||||
74
pkg/types/docker/volume/volume.go
Normal file
74
pkg/types/docker/volume/volume.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package volume
|
||||
|
||||
import "time"
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
// Volume volume
|
||||
// swagger:model Volume
|
||||
type Volume struct {
|
||||
|
||||
// Date/Time the volume was created.
|
||||
CreatedAt time.Time `json:"CreatedAt,omitempty"`
|
||||
|
||||
// Name of the volume driver used by the volume.
|
||||
// Required: true
|
||||
Driver string `json:"Driver"`
|
||||
|
||||
// User-defined key/value metadata.
|
||||
// Required: true
|
||||
Labels map[string]string `json:"Labels"`
|
||||
|
||||
// Mount path of the volume on the host.
|
||||
// Required: true
|
||||
Mountpoint string `json:"Mountpoint"`
|
||||
|
||||
// Name of the volume.
|
||||
// Required: true
|
||||
Name string `json:"Name"`
|
||||
|
||||
// The driver specific options used when creating the volume.
|
||||
//
|
||||
// Required: true
|
||||
Options map[string]string `json:"Options"`
|
||||
|
||||
// The level at which the volume exists. Either `global` for cluster-wide,
|
||||
// or `local` for machine level.
|
||||
//
|
||||
// Required: true
|
||||
Scope string `json:"Scope"`
|
||||
|
||||
// Low-level details about the volume, provided by the volume driver.
|
||||
// Details are returned as a map with key/value pairs:
|
||||
// `{"key":"value","key2":"value2"}`.
|
||||
//
|
||||
// The `Status` field is optional, and is omitted if the volume driver
|
||||
// does not support this feature.
|
||||
//
|
||||
Status map[string]interface{} `json:"Status,omitempty"`
|
||||
|
||||
// usage data
|
||||
UsageData UsageData `json:"UsageData,omitempty"`
|
||||
}
|
||||
|
||||
// UsageData Usage details about the volume. This information is used by the
|
||||
// `GET /system/df` endpoint, and omitted in other endpoints.
|
||||
//
|
||||
// swagger:model UsageData
|
||||
type UsageData struct {
|
||||
|
||||
// The number of containers referencing this volume. This field
|
||||
// is set to `-1` if the reference-count is not available.
|
||||
//
|
||||
// Required: true
|
||||
RefCount int64 `json:"RefCount"`
|
||||
|
||||
// Amount of disk space used by the volume (in bytes). This information
|
||||
// is only available for volumes created with the `"local"` volume
|
||||
// driver. For volumes created with other volume drivers, this field
|
||||
// is set to `-1` ("not available")
|
||||
//
|
||||
// Required: true
|
||||
Size int64 `json:"Size"`
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import Editor from '@guolao/vue-monaco-editor'
|
||||
import { NButton, NDataTable, NDropdown, NInput, NSwitch } from 'naive-ui'
|
||||
import { NButton, NDataTable, NDropdown, NFlex, NInput, NSwitch, NTag } from 'naive-ui'
|
||||
|
||||
import container from '@/api/panel/container'
|
||||
import ContainerCreate from '@/views/container/ContainerCreate.vue'
|
||||
@@ -52,19 +52,32 @@ const columns: any = [
|
||||
})
|
||||
}
|
||||
},
|
||||
{ title: '镜像', key: 'image', minWidth: 300, resizable: true, ellipsis: { tooltip: true } },
|
||||
{
|
||||
title: '镜像',
|
||||
key: 'image',
|
||||
minWidth: 300,
|
||||
resizable: true,
|
||||
render(row: any): any {
|
||||
return h(NTag, null, {
|
||||
default: () => row.image
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '端口(主机->容器)',
|
||||
key: 'ports',
|
||||
width: 100,
|
||||
minWidth: 200,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true },
|
||||
render(row: any) {
|
||||
return row.ports
|
||||
.map((port: any) => {
|
||||
return `${port.IP ? port.IP + ':' : ''}${port.PublicPort}->${port.PrivatePort}/${port.Type}`
|
||||
})
|
||||
.join(', ')
|
||||
render(row: any): any {
|
||||
return h(NFlex, null, {
|
||||
default: () =>
|
||||
row.ports.map((port: any) =>
|
||||
h(NTag, null, {
|
||||
default: () =>
|
||||
`${port.host ? port.host + ':' : ''}${port.container_start}->${port.host_start}/${port.protocol}`
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { NButton, NDataTable, NFlex, NInput, NPopconfirm, NTag } from 'naive-ui'
|
||||
|
||||
import container from '@/api/panel/container'
|
||||
import { formatDateTime } from '@/utils'
|
||||
import type { ImageList } from '@/views/container/types'
|
||||
|
||||
const pullModel = ref({
|
||||
@@ -62,10 +63,12 @@ const columns: any = [
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'created',
|
||||
key: 'created_at',
|
||||
width: 200,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true }
|
||||
render(row: any) {
|
||||
return formatDateTime(row.created_at)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
import { NButton, NDataTable, NFlex, NInput, NPopconfirm, NTag } from 'naive-ui'
|
||||
|
||||
import container from '@/api/panel/container'
|
||||
import { formatDateTime } from '@/utils'
|
||||
import type { NetworkList } from '@/views/container/types'
|
||||
|
||||
const createModel = ref({
|
||||
name: '',
|
||||
driver: 'bridge',
|
||||
ipv4: {
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
subnet: '',
|
||||
gateway: '',
|
||||
ip_range: ''
|
||||
@@ -101,10 +102,12 @@ const columns: any = [
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'created',
|
||||
key: 'created_at',
|
||||
width: 200,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true }
|
||||
render(row: any) {
|
||||
return formatDateTime(row.created_at)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { NButton, NDataTable, NInput, NPopconfirm } from 'naive-ui'
|
||||
|
||||
import container from '@/api/panel/container'
|
||||
import { formatDateTime } from '@/utils'
|
||||
import type { VolumeList } from '@/views/container/types'
|
||||
|
||||
const createModel = ref({
|
||||
@@ -27,7 +28,7 @@ const columns: any = [
|
||||
{ type: 'selection', fixed: 'left' },
|
||||
{
|
||||
title: '名称',
|
||||
key: 'id',
|
||||
key: 'name',
|
||||
minWidth: 150,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true }
|
||||
@@ -48,17 +49,19 @@ const columns: any = [
|
||||
},
|
||||
{
|
||||
title: '挂载点',
|
||||
key: 'mount',
|
||||
key: 'mount_point',
|
||||
resizable: true,
|
||||
minWidth: 150,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'created',
|
||||
key: 'created_at',
|
||||
width: 200,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true }
|
||||
render(row: any) {
|
||||
return formatDateTime(row.created_at)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
||||
Reference in New Issue
Block a user