diff --git a/app/http/controllers/container_controller.go b/app/http/controllers/container_controller.go index 4891b20e..8e3c4753 100644 --- a/app/http/controllers/container_controller.go +++ b/app/http/controllers/container_controller.go @@ -5,10 +5,15 @@ import ( "strconv" "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/volume" "github.com/docker/go-connections/nat" "github.com/goravel/framework/contracts/http" + + commonrequests "panel/app/http/requests/common" requests "panel/app/http/requests/container" "panel/internal/services" ) @@ -23,17 +28,61 @@ func NewContainerController() *ContainerController { } } +// ContainerList +// +// @Summary 获取容器列表 +// @Description 获取所有容器列表 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query commonrequests.Paginate true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/list [get] func (r *ContainerController) ContainerList(ctx http.Context) http.Response { + var request commonrequests.Paginate + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + containers, err := r.container.ContainerListAll() if err != nil { return Error(ctx, http.StatusInternalServerError, err.Error()) } - return Success(ctx, containers) + startIndex := (request.Page - 1) * request.Limit + endIndex := request.Page * request.Limit + if startIndex > len(containers) { + return Success(ctx, http.Json{ + "total": 0, + "items": []types.Container{}, + }) + } + if endIndex > len(containers) { + endIndex = len(containers) + } + paged := containers[startIndex:endIndex] + if paged == nil { + paged = []types.Container{} + } + + return Success(ctx, http.Json{ + "total": len(containers), + "items": paged, + }) } +// ContainerSearch +// +// @Summary 搜索容器 +// @Description 根据容器名称搜索容器 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param name query string true "容器名称" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/search [get] func (r *ContainerController) ContainerSearch(ctx http.Context) http.Response { - fields := strings.Fields(ctx.Request().Query("names")) + fields := strings.Fields(ctx.Request().Query("name")) containers, err := r.container.ContainerListByNames(fields) if err != nil { return Error(ctx, http.StatusInternalServerError, err.Error()) @@ -42,6 +91,17 @@ func (r *ContainerController) ContainerSearch(ctx http.Context) http.Response { return Success(ctx, containers) } +// ContainerCreate +// +// @Summary 创建容器 +// @Description 创建一个容器 +// @Tags 容器 +// @Accept json +// @Produce json +// @Security BearerToken +// @Param data body requests.ContainerCreate true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/create [post] func (r *ContainerController) ContainerCreate(ctx http.Context) http.Response { var request requests.ContainerCreate if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -125,6 +185,16 @@ func (r *ContainerController) ContainerCreate(ctx http.Context) http.Response { return Success(ctx, id) } +// ContainerRemove +// +// @Summary 删除容器 +// @Description 删除一个容器 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data body requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/remove [post] func (r *ContainerController) ContainerRemove(ctx http.Context) http.Response { var request requests.ID if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -138,6 +208,16 @@ func (r *ContainerController) ContainerRemove(ctx http.Context) http.Response { return Success(ctx, nil) } +// ContainerStart +// +// @Summary 启动容器 +// @Description 启动一个容器 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data body requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/start [post] func (r *ContainerController) ContainerStart(ctx http.Context) http.Response { var request requests.ID if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -151,6 +231,16 @@ func (r *ContainerController) ContainerStart(ctx http.Context) http.Response { return Success(ctx, nil) } +// ContainerStop +// +// @Summary 停止容器 +// @Description 停止一个容器 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data body requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/stop [post] func (r *ContainerController) ContainerStop(ctx http.Context) http.Response { var request requests.ID if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -164,6 +254,16 @@ func (r *ContainerController) ContainerStop(ctx http.Context) http.Response { return Success(ctx, nil) } +// ContainerRestart +// +// @Summary 重启容器 +// @Description 重启一个容器 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data body requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/restart [post] func (r *ContainerController) ContainerRestart(ctx http.Context) http.Response { var request requests.ID if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -177,6 +277,15 @@ func (r *ContainerController) ContainerRestart(ctx http.Context) http.Response { return Success(ctx, nil) } +// ContainerPause +// +// @Summary 暂停容器 +// @Description 暂停一个容器 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data body requests.ID true "request" +// @Success 200 {object} SuccessResponse func (r *ContainerController) ContainerPause(ctx http.Context) http.Response { var request requests.ID if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -190,6 +299,17 @@ func (r *ContainerController) ContainerPause(ctx http.Context) http.Response { return Success(ctx, nil) } +// ContainerUnpause +// +// @Summary 取消暂停容器 +// @Description 取消暂停一个容器 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data body requests.ID true "request" +// @Success 200 {object} SuccessResponse +// +// @Router /panel/container/unpause [post] func (r *ContainerController) ContainerUnpause(ctx http.Context) http.Response { var request requests.ID if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -203,6 +323,16 @@ func (r *ContainerController) ContainerUnpause(ctx http.Context) http.Response { return Success(ctx, nil) } +// ContainerInspect +// +// @Summary 查看容器 +// @Description 查看一个容器的详细信息 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/inspect [get] func (r *ContainerController) ContainerInspect(ctx http.Context) http.Response { var request requests.ID if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -217,6 +347,16 @@ func (r *ContainerController) ContainerInspect(ctx http.Context) http.Response { return Success(ctx, data) } +// ContainerKill +// +// @Summary 杀死容器 +// @Description 杀死一个容器 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data body requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/kill [post] func (r *ContainerController) ContainerKill(ctx http.Context) http.Response { var request requests.ID if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -230,6 +370,16 @@ func (r *ContainerController) ContainerKill(ctx http.Context) http.Response { return Success(ctx, nil) } +// ContainerRename +// +// @Summary 重命名容器 +// @Description 重命名一个容器 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data body requests.ContainerRename true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/rename [post] func (r *ContainerController) ContainerRename(ctx http.Context) http.Response { var request requests.ContainerRename if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -243,6 +393,16 @@ func (r *ContainerController) ContainerRename(ctx http.Context) http.Response { return Success(ctx, nil) } +// ContainerStats +// +// @Summary 查看容器状态 +// @Description 查看一个容器的状态信息 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/stats [get] func (r *ContainerController) ContainerStats(ctx http.Context) http.Response { var request requests.ID if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -257,6 +417,16 @@ func (r *ContainerController) ContainerStats(ctx http.Context) http.Response { return Success(ctx, data) } +// ContainerExist +// +// @Summary 检查容器是否存在 +// @Description 检查一个容器是否存在 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/exist [get] func (r *ContainerController) ContainerExist(ctx http.Context) http.Response { var request requests.ID if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -271,6 +441,16 @@ func (r *ContainerController) ContainerExist(ctx http.Context) http.Response { return Success(ctx, exist) } +// ContainerLogs +// +// @Summary 查看容器日志 +// @Description 查看一个容器的日志 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/logs [get] func (r *ContainerController) ContainerLogs(ctx http.Context) http.Response { var request requests.ID if sanitize := Sanitize(ctx, &request); sanitize != nil { @@ -285,6 +465,15 @@ func (r *ContainerController) ContainerLogs(ctx http.Context) http.Response { return Success(ctx, data) } +// ContainerPrune +// +// @Summary 清理容器 +// @Description 清理无用的容器 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Success 200 {object} SuccessResponse +// @Router /panel/container/prune [post] func (r *ContainerController) ContainerPrune(ctx http.Context) http.Response { if err := r.container.ContainerPrune(); err != nil { return Error(ctx, http.StatusInternalServerError, err.Error()) @@ -293,92 +482,517 @@ func (r *ContainerController) ContainerPrune(ctx http.Context) http.Response { return Success(ctx, nil) } +// NetworkList +// +// @Summary 获取网络列表 +// @Description 获取所有网络列表 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query commonrequests.Paginate true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/network/list [get] func (r *ContainerController) NetworkList(ctx http.Context) http.Response { + var request commonrequests.Paginate + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + networks, err := r.container.NetworkList() if err != nil { return Error(ctx, http.StatusInternalServerError, err.Error()) } - return Success(ctx, networks) + startIndex := (request.Page - 1) * request.Limit + endIndex := request.Page * request.Limit + if startIndex > len(networks) { + return Success(ctx, http.Json{ + "total": 0, + "items": []types.NetworkResource{}, + }) + } + if endIndex > len(networks) { + endIndex = len(networks) + } + paged := networks[startIndex:endIndex] + if paged == nil { + paged = []types.NetworkResource{} + } + + return Success(ctx, http.Json{ + "total": len(networks), + "items": paged, + }) } +// NetworkCreate +// +// @Summary 创建网络 +// @Description 创建一个网络 +// @Tags 容器 +// @Accept json +// @Produce json +// @Security BearerToken +// @Param data body requests.NetworkCreate true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/network/create [post] func (r *ContainerController) NetworkCreate(ctx http.Context) http.Response { - return Success(ctx, nil) + var request requests.NetworkCreate + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + id, err := r.container.NetworkCreate(request) + if err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + + return Success(ctx, id) } +// NetworkRemove +// +// @Summary 删除网络 +// @Description 删除一个网络 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data body requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/network/remove [post] func (r *ContainerController) NetworkRemove(ctx http.Context) http.Response { + var request requests.ID + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + if err := r.container.NetworkRemove(request.ID); err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + return Success(ctx, nil) } +// NetworkExist +// +// @Summary 检查网络是否存在 +// @Description 检查一个网络是否存在 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/network/exist [get] func (r *ContainerController) NetworkExist(ctx http.Context) http.Response { - return Success(ctx, nil) + var request requests.ID + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + exist, err := r.container.NetworkExist(request.ID) + if err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + + return Success(ctx, exist) } +// NetworkInspect +// +// @Summary 查看网络 +// @Description 查看一个网络的详细信息 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/network/inspect [get] func (r *ContainerController) NetworkInspect(ctx http.Context) http.Response { - return Success(ctx, nil) + var request requests.ID + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + data, err := r.container.NetworkInspect(request.ID) + if err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + + return Success(ctx, data) } +// NetworkConnect +// +// @Summary 连接容器到网络 +// @Description 连接一个容器到一个网络 +// @Tags 容器 +// @Accept json +// @Produce json +// @Security BearerToken +// @Param data body requests.NetworkConnectDisConnect true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/network/connect [post] func (r *ContainerController) NetworkConnect(ctx http.Context) http.Response { + var request requests.NetworkConnectDisConnect + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + if err := r.container.NetworkConnect(request.Network, request.Container); err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + return Success(ctx, nil) } +// NetworkDisconnect +// +// @Summary 从网络断开容器 +// @Description 从一个网络断开一个容器 +// @Tags 容器 +// @Accept json +// @Produce json +// @Security BearerToken +// @Param data body requests.NetworkConnectDisConnect true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/network/disconnect [post] func (r *ContainerController) NetworkDisconnect(ctx http.Context) http.Response { + var request requests.NetworkConnectDisConnect + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + if err := r.container.NetworkDisconnect(request.Network, request.Container); err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + return Success(ctx, nil) } +// NetworkPrune +// +// @Summary 清理网络 +// @Description 清理无用的网络 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Success 200 {object} SuccessResponse +// @Router /panel/container/network/prune [post] func (r *ContainerController) NetworkPrune(ctx http.Context) http.Response { + if err := r.container.NetworkPrune(); err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + return Success(ctx, nil) } +// ImageList +// +// @Summary 获取镜像列表 +// @Description 获取所有镜像列表 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query commonrequests.Paginate true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/image/list [get] func (r *ContainerController) ImageList(ctx http.Context) http.Response { - return Success(ctx, nil) + var request commonrequests.Paginate + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + images, err := r.container.ImageList() + if err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + + startIndex := (request.Page - 1) * request.Limit + endIndex := request.Page * request.Limit + if startIndex > len(images) { + return Success(ctx, http.Json{ + "total": 0, + "items": []image.Summary{}, + }) + } + if endIndex > len(images) { + endIndex = len(images) + } + paged := images[startIndex:endIndex] + if paged == nil { + paged = []image.Summary{} + } + + return Success(ctx, http.Json{ + "total": len(images), + "items": paged, + }) } +// ImageExist +// +// @Summary 检查镜像是否存在 +// @Description 检查一个镜像是否存在 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/image/exist [get] func (r *ContainerController) ImageExist(ctx http.Context) http.Response { - return Success(ctx, nil) + var request requests.ID + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + exist, err := r.container.ImageExist(request.ID) + if err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + + return Success(ctx, exist) } +// ImagePull +// +// @Summary 拉取镜像 +// @Description 拉取一个镜像 +// @Tags 容器 +// @Accept json +// @Produce json +// @Security BearerToken +// @Param data body requests.ImagePull true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/image/pull [post] func (r *ContainerController) ImagePull(ctx http.Context) http.Response { + var request requests.ImagePull + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + if err := r.container.ImagePull(request); err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + return Success(ctx, nil) } +// ImageRemove +// +// @Summary 删除镜像 +// @Description 删除一个镜像 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data body requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/image/remove [post] func (r *ContainerController) ImageRemove(ctx http.Context) http.Response { + var request requests.ID + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + if err := r.container.ImageRemove(request.ID); err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + return Success(ctx, nil) } +// ImagePrune +// +// @Summary 清理镜像 +// @Description 清理无用的镜像 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Success 200 {object} SuccessResponse +// @Router /panel/container/image/prune [post] func (r *ContainerController) ImagePrune(ctx http.Context) http.Response { + if err := r.container.ImagePrune(); err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + return Success(ctx, nil) } +// ImageInspect +// +// @Summary 查看镜像 +// @Description 查看一个镜像的详细信息 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/image/inspect [get] func (r *ContainerController) ImageInspect(ctx http.Context) http.Response { - return Success(ctx, nil) + var request requests.ID + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + data, err := r.container.ImageInspect(request.ID) + if err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + + return Success(ctx, data) } +// VolumeList +// +// @Summary 获取卷列表 +// @Description 获取所有卷列表 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query commonrequests.Paginate true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/volume/list [get] func (r *ContainerController) VolumeList(ctx http.Context) http.Response { + var request commonrequests.Paginate + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + volumes, err := r.container.VolumeList() if err != nil { return Error(ctx, http.StatusInternalServerError, err.Error()) } - return Success(ctx, volumes) + startIndex := (request.Page - 1) * request.Limit + endIndex := request.Page * request.Limit + if startIndex > len(volumes) { + return Success(ctx, http.Json{ + "total": 0, + "items": []*volume.Volume{}, + }) + } + if endIndex > len(volumes) { + endIndex = len(volumes) + } + paged := volumes[startIndex:endIndex] + if paged == nil { + paged = []*volume.Volume{} + } + + return Success(ctx, http.Json{ + "total": len(volumes), + "items": paged, + }) } +// VolumeCreate +// +// @Summary 创建卷 +// @Description 创建一个卷 +// @Tags 容器 +// @Accept json +// @Produce json +// @Security BearerToken +// @Param data body requests.VolumeCreate true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/volume/create [post] func (r *ContainerController) VolumeCreate(ctx http.Context) http.Response { - return Success(ctx, nil) + var request requests.VolumeCreate + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + data, err := r.container.VolumeCreate(request) + if err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + + return Success(ctx, data.Name) } +// VolumeExist +// +// @Summary 检查卷是否存在 +// @Description 检查一个卷是否存在 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/volume/exist [get] func (r *ContainerController) VolumeExist(ctx http.Context) http.Response { - return Success(ctx, nil) + var request requests.ID + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + exist, err := r.container.VolumeExist(request.ID) + if err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + + return Success(ctx, exist) } +// VolumeInspect +// +// @Summary 查看卷 +// @Description 查看一个卷的详细信息 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data query requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/volume/inspect [get] func (r *ContainerController) VolumeInspect(ctx http.Context) http.Response { - return Success(ctx, nil) + var request requests.ID + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + data, err := r.container.VolumeInspect(request.ID) + if err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + + return Success(ctx, data) } +// VolumeRemove +// +// @Summary 删除卷 +// @Description 删除一个卷 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Param data body requests.ID true "request" +// @Success 200 {object} SuccessResponse +// @Router /panel/container/volume/remove [post] func (r *ContainerController) VolumeRemove(ctx http.Context) http.Response { + var request requests.ID + if sanitize := Sanitize(ctx, &request); sanitize != nil { + return sanitize + } + + if err := r.container.VolumeRemove(request.ID); err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + return Success(ctx, nil) } +// VolumePrune +// +// @Summary 清理卷 +// @Description 清理无用的卷 +// @Tags 容器 +// @Produce json +// @Security BearerToken +// @Success 200 {object} SuccessResponse +// @Router /panel/container/volume/prune [post] func (r *ContainerController) VolumePrune(ctx http.Context) http.Response { + if err := r.container.VolumePrune(); err != nil { + return Error(ctx, http.StatusInternalServerError, err.Error()) + } + return Success(ctx, nil) } diff --git a/app/http/requests/container/image_pull.go b/app/http/requests/container/image_pull.go new file mode 100644 index 00000000..0849fc4b --- /dev/null +++ b/app/http/requests/container/image_pull.go @@ -0,0 +1,38 @@ +package requests + +import ( + "github.com/goravel/framework/contracts/http" + "github.com/goravel/framework/contracts/validation" +) + +type ImagePull struct { + Name string `form:"name" json:"name"` + Auth bool `form:"auth" json:"auth"` + Username string `form:"username" json:"username"` + Password string `form:"password" json:"password"` +} + +func (r *ImagePull) Authorize(ctx http.Context) error { + return nil +} + +func (r *ImagePull) Rules(ctx http.Context) map[string]string { + return map[string]string{ + "name": "required|string", + "auth": "bool", + "username": "string", + "password": "string", + } +} + +func (r *ImagePull) Messages(ctx http.Context) map[string]string { + return map[string]string{} +} + +func (r *ImagePull) Attributes(ctx http.Context) map[string]string { + return map[string]string{} +} + +func (r *ImagePull) PrepareForValidation(ctx http.Context, data validation.Data) error { + return nil +} diff --git a/app/http/requests/container/network_connect_disconnect.go b/app/http/requests/container/network_connect_disconnect.go new file mode 100644 index 00000000..eb05f9f7 --- /dev/null +++ b/app/http/requests/container/network_connect_disconnect.go @@ -0,0 +1,34 @@ +package requests + +import ( + "github.com/goravel/framework/contracts/http" + "github.com/goravel/framework/contracts/validation" +) + +type NetworkConnectDisConnect struct { + Network string `form:"network" json:"network"` + Container string `form:"container" json:"container"` +} + +func (r *NetworkConnectDisConnect) Authorize(ctx http.Context) error { + return nil +} + +func (r *NetworkConnectDisConnect) Rules(ctx http.Context) map[string]string { + return map[string]string{ + "network": "required|string", + "container": "required|string", + } +} + +func (r *NetworkConnectDisConnect) Messages(ctx http.Context) map[string]string { + return map[string]string{} +} + +func (r *NetworkConnectDisConnect) Attributes(ctx http.Context) map[string]string { + return map[string]string{} +} + +func (r *NetworkConnectDisConnect) PrepareForValidation(ctx http.Context, data validation.Data) error { + return nil +} diff --git a/app/http/requests/container/network_create.go b/app/http/requests/container/network_create.go new file mode 100644 index 00000000..2916760d --- /dev/null +++ b/app/http/requests/container/network_create.go @@ -0,0 +1,42 @@ +package requests + +import ( + "github.com/goravel/framework/contracts/http" + "github.com/goravel/framework/contracts/validation" +) + +type NetworkCreate struct { + Name string `form:"name" json:"name"` + Driver string `form:"driver" json:"driver"` + Ipv4 Network `form:"ipv4" json:"ipv4"` + Ipv6 Network `form:"ipv6" json:"ipv6"` + Labels []string `form:"labels" json:"labels"` + Options []string `form:"options" json:"options"` +} + +func (r *NetworkCreate) Authorize(ctx http.Context) error { + return nil +} + +func (r *NetworkCreate) Rules(ctx http.Context) map[string]string { + return map[string]string{ + "name": "required|string", + "driver": "required|string|in:bridge,overlay,macvlan,ipvlan", + "ipv4": "required", + "ipv6": "required", + "labels": "slice", + "options": "slice", + } +} + +func (r *NetworkCreate) Messages(ctx http.Context) map[string]string { + return map[string]string{} +} + +func (r *NetworkCreate) Attributes(ctx http.Context) map[string]string { + return map[string]string{} +} + +func (r *NetworkCreate) PrepareForValidation(ctx http.Context, data validation.Data) error { + return nil +} diff --git a/app/http/requests/container/types.go b/app/http/requests/container/types.go index 016b8473..78cf97fd 100644 --- a/app/http/requests/container/types.go +++ b/app/http/requests/container/types.go @@ -1,16 +1,23 @@ package requests type ContainerPort struct { - ContainerStart int `json:"start"` - ContainerEnd int `json:"end"` - Host string `json:"host"` - HostStart int `json:"hostStart"` - HostEnd int `json:"hostEnd"` - Protocol string `json:"protocol"` + ContainerStart int `form:"start" json:"start"` + ContainerEnd int `form:"end" json:"end"` + Host string `form:"host" json:"host"` + HostStart int `form:"host_start" json:"host_start"` + HostEnd int `form:"host_end" json:"host_end"` + Protocol string `form:"protocol" json:"protocol"` } type ContainerVolume struct { - Host string `json:"host"` - Container string `json:"container"` - Mode string `json:"mode"` + Host string `form:"start" json:"host"` + Container string `form:"start" json:"container"` + Mode string `form:"start" json:"mode"` +} + +type Network struct { + Enabled bool `form:"enabled" json:"enabled"` + Gateway string `form:"gateway" json:"gateway"` + IPRange string `form:"ip_range" json:"ip_range"` + Subnet string `form:"subnet" json:"subnet"` } diff --git a/app/http/requests/container/volume_create.go b/app/http/requests/container/volume_create.go new file mode 100644 index 00000000..b26e3130 --- /dev/null +++ b/app/http/requests/container/volume_create.go @@ -0,0 +1,38 @@ +package requests + +import ( + "github.com/goravel/framework/contracts/http" + "github.com/goravel/framework/contracts/validation" +) + +type VolumeCreate struct { + Name string `form:"name" json:"name"` + Driver string `form:"driver" json:"driver"` + Labels []string `form:"labels" json:"labels"` + Options []string `form:"options" json:"options"` +} + +func (r *VolumeCreate) Authorize(ctx http.Context) error { + return nil +} + +func (r *VolumeCreate) Rules(ctx http.Context) map[string]string { + return map[string]string{ + "name": "required|string", + "driver": "required|string|in:local", + "labels": "slice", + "options": "slice", + } +} + +func (r *VolumeCreate) Messages(ctx http.Context) map[string]string { + return map[string]string{} +} + +func (r *VolumeCreate) Attributes(ctx http.Context) map[string]string { + return map[string]string{} +} + +func (r *VolumeCreate) PrepareForValidation(ctx http.Context, data validation.Data) error { + return nil +} diff --git a/docs/docs.go b/docs/docs.go index f6550f7a..8e6839c4 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -825,6 +825,1202 @@ const docTemplate = `{ } } }, + "/panel/container/create": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "创建一个容器", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "创建容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.ContainerCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/exist": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "检查一个容器是否存在", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "检查容器是否存在", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/image/exist": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "检查一个镜像是否存在", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "检查镜像是否存在", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/image/inspect": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "查看一个镜像的详细信息", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "查看镜像", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/image/list": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "获取所有镜像列表", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "获取镜像列表", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/image/prune": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "清理无用的镜像", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "清理镜像", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/image/pull": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "拉取一个镜像", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "拉取镜像", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.ImagePull" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/image/remove": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "删除一个镜像", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "删除镜像", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/inspect": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "查看一个容器的详细信息", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "查看容器", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/kill": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "杀死一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "杀死容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/list": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "获取所有容器列表", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "获取容器列表", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/logs": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "查看一个容器的日志", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "查看容器日志", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/connect": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "连接一个容器到一个网络", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "连接容器到网络", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.NetworkConnectDisConnect" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/create": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "创建一个网络", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "创建网络", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.NetworkCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/disconnect": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "从一个网络断开一个容器", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "从网络断开容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.NetworkConnectDisConnect" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/exist": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "检查一个网络是否存在", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "检查网络是否存在", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/inspect": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "查看一个网络的详细信息", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "查看网络", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/list": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "获取所有网络列表", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "获取网络列表", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/prune": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "清理无用的网络", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "清理网络", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/remove": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "删除一个网络", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "删除网络", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/prune": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "清理无用的容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "清理容器", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/remove": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "删除一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "删除容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/rename": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "重命名一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "重命名容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.ContainerRename" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/restart": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "重启一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "重启容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/search": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "根据容器名称搜索容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "搜索容器", + "parameters": [ + { + "type": "string", + "description": "容器名称", + "name": "name", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/start": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "启动一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "启动容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/stats": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "查看一个容器的状态信息", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "查看容器状态", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/stop": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "停止一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "停止容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/unpause": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "取消暂停一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "取消暂停容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/volume/create": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "创建一个卷", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "创建卷", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.VolumeCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/volume/exist": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "检查一个卷是否存在", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "检查卷是否存在", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/volume/inspect": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "查看一个卷的详细信息", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "查看卷", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/volume/list": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "获取所有卷列表", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "获取卷列表", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/volume/prune": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "清理无用的卷", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "清理卷", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/volume/remove": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "删除一个卷", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "删除卷", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, "/panel/file/archive": { "post": { "security": [ @@ -2760,6 +3956,14 @@ const docTemplate = `{ } } }, + "panel_app_http_requests_container.ID": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + }, "panel_app_http_requests_plugins_rsync.Update": { "type": "object", "properties": { @@ -2925,6 +4129,131 @@ const docTemplate = `{ } } }, + "requests.ContainerCreate": { + "type": "object", + "properties": { + "auto_remove": { + "type": "boolean" + }, + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "cpu_shares": { + "type": "integer" + }, + "cpus": { + "type": "integer" + }, + "entrypoint": { + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "type": "array", + "items": { + "type": "string" + } + }, + "image": { + "type": "string" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "memory": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "network": { + "type": "string" + }, + "open_stdin": { + "type": "boolean" + }, + "ports": { + "type": "array", + "items": { + "$ref": "#/definitions/requests.ContainerPort" + } + }, + "privileged": { + "type": "boolean" + }, + "publish_all_ports": { + "type": "boolean" + }, + "restart_policy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumes": { + "type": "array", + "items": { + "$ref": "#/definitions/requests.ContainerVolume" + } + } + } + }, + "requests.ContainerPort": { + "type": "object", + "properties": { + "end": { + "type": "integer" + }, + "host": { + "type": "string" + }, + "host_end": { + "type": "integer" + }, + "host_start": { + "type": "integer" + }, + "protocol": { + "type": "string" + }, + "start": { + "type": "integer" + } + } + }, + "requests.ContainerRename": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "requests.ContainerVolume": { + "type": "object", + "properties": { + "container": { + "type": "string" + }, + "host": { + "type": "string" + }, + "mode": { + "type": "string" + } + } + }, "requests.Copy": { "type": "object", "properties": { @@ -3006,6 +4335,23 @@ const docTemplate = `{ } } }, + "requests.ImagePull": { + "type": "object", + "properties": { + "auth": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "requests.Login": { "type": "object", "properties": { @@ -3028,6 +4374,63 @@ const docTemplate = `{ } } }, + "requests.Network": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "gateway": { + "type": "string" + }, + "ip_range": { + "type": "string" + }, + "subnet": { + "type": "string" + } + } + }, + "requests.NetworkConnectDisConnect": { + "type": "object", + "properties": { + "container": { + "type": "string" + }, + "network": { + "type": "string" + } + } + }, + "requests.NetworkCreate": { + "type": "object", + "properties": { + "driver": { + "type": "string" + }, + "ipv4": { + "$ref": "#/definitions/requests.Network" + }, + "ipv6": { + "$ref": "#/definitions/requests.Network" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "requests.NotExist": { "type": "object", "properties": { @@ -3220,6 +4623,29 @@ const docTemplate = `{ "type": "string" } } + }, + "requests.VolumeCreate": { + "type": "object", + "properties": { + "driver": { + "type": "string" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "string" + } + } + } } }, "securityDefinitions": { diff --git a/docs/swagger.json b/docs/swagger.json index 874e0d50..d2078235 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -818,6 +818,1202 @@ } } }, + "/panel/container/create": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "创建一个容器", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "创建容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.ContainerCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/exist": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "检查一个容器是否存在", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "检查容器是否存在", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/image/exist": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "检查一个镜像是否存在", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "检查镜像是否存在", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/image/inspect": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "查看一个镜像的详细信息", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "查看镜像", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/image/list": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "获取所有镜像列表", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "获取镜像列表", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/image/prune": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "清理无用的镜像", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "清理镜像", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/image/pull": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "拉取一个镜像", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "拉取镜像", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.ImagePull" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/image/remove": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "删除一个镜像", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "删除镜像", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/inspect": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "查看一个容器的详细信息", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "查看容器", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/kill": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "杀死一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "杀死容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/list": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "获取所有容器列表", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "获取容器列表", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/logs": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "查看一个容器的日志", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "查看容器日志", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/connect": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "连接一个容器到一个网络", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "连接容器到网络", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.NetworkConnectDisConnect" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/create": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "创建一个网络", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "创建网络", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.NetworkCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/disconnect": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "从一个网络断开一个容器", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "从网络断开容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.NetworkConnectDisConnect" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/exist": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "检查一个网络是否存在", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "检查网络是否存在", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/inspect": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "查看一个网络的详细信息", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "查看网络", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/list": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "获取所有网络列表", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "获取网络列表", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/prune": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "清理无用的网络", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "清理网络", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/network/remove": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "删除一个网络", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "删除网络", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/prune": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "清理无用的容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "清理容器", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/remove": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "删除一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "删除容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/rename": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "重命名一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "重命名容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.ContainerRename" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/restart": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "重启一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "重启容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/search": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "根据容器名称搜索容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "搜索容器", + "parameters": [ + { + "type": "string", + "description": "容器名称", + "name": "name", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/start": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "启动一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "启动容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/stats": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "查看一个容器的状态信息", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "查看容器状态", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/stop": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "停止一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "停止容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/unpause": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "取消暂停一个容器", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "取消暂停容器", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/volume/create": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "创建一个卷", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "创建卷", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.VolumeCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/volume/exist": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "检查一个卷是否存在", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "检查卷是否存在", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/volume/inspect": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "查看一个卷的详细信息", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "查看卷", + "parameters": [ + { + "type": "string", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/volume/list": { + "get": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "获取所有卷列表", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "获取卷列表", + "parameters": [ + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/volume/prune": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "清理无用的卷", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "清理卷", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, + "/panel/container/volume/remove": { + "post": { + "security": [ + { + "BearerToken": [] + } + ], + "description": "删除一个卷", + "produces": [ + "application/json" + ], + "tags": [ + "容器" + ], + "summary": "删除卷", + "parameters": [ + { + "description": "request", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/panel_app_http_requests_container.ID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.SuccessResponse" + } + } + } + } + }, "/panel/file/archive": { "post": { "security": [ @@ -2753,6 +3949,14 @@ } } }, + "panel_app_http_requests_container.ID": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + }, "panel_app_http_requests_plugins_rsync.Update": { "type": "object", "properties": { @@ -2918,6 +4122,131 @@ } } }, + "requests.ContainerCreate": { + "type": "object", + "properties": { + "auto_remove": { + "type": "boolean" + }, + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "cpu_shares": { + "type": "integer" + }, + "cpus": { + "type": "integer" + }, + "entrypoint": { + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "type": "array", + "items": { + "type": "string" + } + }, + "image": { + "type": "string" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "memory": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "network": { + "type": "string" + }, + "open_stdin": { + "type": "boolean" + }, + "ports": { + "type": "array", + "items": { + "$ref": "#/definitions/requests.ContainerPort" + } + }, + "privileged": { + "type": "boolean" + }, + "publish_all_ports": { + "type": "boolean" + }, + "restart_policy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumes": { + "type": "array", + "items": { + "$ref": "#/definitions/requests.ContainerVolume" + } + } + } + }, + "requests.ContainerPort": { + "type": "object", + "properties": { + "end": { + "type": "integer" + }, + "host": { + "type": "string" + }, + "host_end": { + "type": "integer" + }, + "host_start": { + "type": "integer" + }, + "protocol": { + "type": "string" + }, + "start": { + "type": "integer" + } + } + }, + "requests.ContainerRename": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "requests.ContainerVolume": { + "type": "object", + "properties": { + "container": { + "type": "string" + }, + "host": { + "type": "string" + }, + "mode": { + "type": "string" + } + } + }, "requests.Copy": { "type": "object", "properties": { @@ -2999,6 +4328,23 @@ } } }, + "requests.ImagePull": { + "type": "object", + "properties": { + "auth": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "requests.Login": { "type": "object", "properties": { @@ -3021,6 +4367,63 @@ } } }, + "requests.Network": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "gateway": { + "type": "string" + }, + "ip_range": { + "type": "string" + }, + "subnet": { + "type": "string" + } + } + }, + "requests.NetworkConnectDisConnect": { + "type": "object", + "properties": { + "container": { + "type": "string" + }, + "network": { + "type": "string" + } + } + }, + "requests.NetworkCreate": { + "type": "object", + "properties": { + "driver": { + "type": "string" + }, + "ipv4": { + "$ref": "#/definitions/requests.Network" + }, + "ipv6": { + "$ref": "#/definitions/requests.Network" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "requests.NotExist": { "type": "object", "properties": { @@ -3213,6 +4616,29 @@ "type": "string" } } + }, + "requests.VolumeCreate": { + "type": "object", + "properties": { + "driver": { + "type": "string" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "string" + } + } + } } }, "securityDefinitions": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 2c3e9b2a..d23537f8 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -180,6 +180,11 @@ definitions: updated_at: type: string type: object + panel_app_http_requests_container.ID: + properties: + id: + type: string + type: object panel_app_http_requests_plugins_rsync.Update: properties: auth_user: @@ -288,6 +293,88 @@ definitions: website_id: type: integer type: object + requests.ContainerCreate: + properties: + auto_remove: + type: boolean + command: + items: + type: string + type: array + cpu_shares: + type: integer + cpus: + type: integer + entrypoint: + items: + type: string + type: array + env: + items: + type: string + type: array + image: + type: string + labels: + items: + type: string + type: array + memory: + type: integer + name: + type: string + network: + type: string + open_stdin: + type: boolean + ports: + items: + $ref: '#/definitions/requests.ContainerPort' + type: array + privileged: + type: boolean + publish_all_ports: + type: boolean + restart_policy: + type: string + tty: + type: boolean + volumes: + items: + $ref: '#/definitions/requests.ContainerVolume' + type: array + type: object + requests.ContainerPort: + properties: + end: + type: integer + host: + type: string + host_end: + type: integer + host_start: + type: integer + protocol: + type: string + start: + type: integer + type: object + requests.ContainerRename: + properties: + id: + type: string + name: + type: string + type: object + requests.ContainerVolume: + properties: + container: + type: string + host: + type: string + mode: + type: string + type: object requests.Copy: properties: new: @@ -340,6 +427,17 @@ definitions: path: type: string type: object + requests.ImagePull: + properties: + auth: + type: boolean + name: + type: string + password: + type: string + username: + type: string + type: object requests.Login: properties: password: @@ -354,6 +452,43 @@ definitions: old: type: string type: object + requests.Network: + properties: + enabled: + type: boolean + gateway: + type: string + ip_range: + type: string + subnet: + type: string + type: object + requests.NetworkConnectDisConnect: + properties: + container: + type: string + network: + type: string + type: object + requests.NetworkCreate: + properties: + driver: + type: string + ipv4: + $ref: '#/definitions/requests.Network' + ipv6: + $ref: '#/definitions/requests.Network' + labels: + items: + type: string + type: array + name: + type: string + options: + items: + type: string + type: array + type: object requests.NotExist: properties: path: @@ -479,6 +614,21 @@ definitions: kid: type: string type: object + requests.VolumeCreate: + properties: + driver: + type: string + labels: + items: + type: string + type: array + name: + type: string + options: + items: + type: string + type: array + type: object info: contact: email: i@haozi.net @@ -976,6 +1126,729 @@ paths: summary: 更新 ACME 用户 tags: - 证书管理 + /panel/container/create: + post: + consumes: + - application/json + description: 创建一个容器 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/requests.ContainerCreate' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 创建容器 + tags: + - 容器 + /panel/container/exist: + get: + description: 检查一个容器是否存在 + parameters: + - in: query + name: id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 检查容器是否存在 + tags: + - 容器 + /panel/container/image/exist: + get: + description: 检查一个镜像是否存在 + parameters: + - in: query + name: id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 检查镜像是否存在 + tags: + - 容器 + /panel/container/image/inspect: + get: + description: 查看一个镜像的详细信息 + parameters: + - in: query + name: id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 查看镜像 + tags: + - 容器 + /panel/container/image/list: + get: + description: 获取所有镜像列表 + parameters: + - in: query + name: limit + type: integer + - in: query + name: page + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 获取镜像列表 + tags: + - 容器 + /panel/container/image/prune: + post: + description: 清理无用的镜像 + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 清理镜像 + tags: + - 容器 + /panel/container/image/pull: + post: + consumes: + - application/json + description: 拉取一个镜像 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/requests.ImagePull' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 拉取镜像 + tags: + - 容器 + /panel/container/image/remove: + post: + description: 删除一个镜像 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/panel_app_http_requests_container.ID' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 删除镜像 + tags: + - 容器 + /panel/container/inspect: + get: + description: 查看一个容器的详细信息 + parameters: + - in: query + name: id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 查看容器 + tags: + - 容器 + /panel/container/kill: + post: + description: 杀死一个容器 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/panel_app_http_requests_container.ID' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 杀死容器 + tags: + - 容器 + /panel/container/list: + get: + description: 获取所有容器列表 + parameters: + - in: query + name: limit + type: integer + - in: query + name: page + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 获取容器列表 + tags: + - 容器 + /panel/container/logs: + get: + description: 查看一个容器的日志 + parameters: + - in: query + name: id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 查看容器日志 + tags: + - 容器 + /panel/container/network/connect: + post: + consumes: + - application/json + description: 连接一个容器到一个网络 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/requests.NetworkConnectDisConnect' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 连接容器到网络 + tags: + - 容器 + /panel/container/network/create: + post: + consumes: + - application/json + description: 创建一个网络 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/requests.NetworkCreate' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 创建网络 + tags: + - 容器 + /panel/container/network/disconnect: + post: + consumes: + - application/json + description: 从一个网络断开一个容器 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/requests.NetworkConnectDisConnect' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 从网络断开容器 + tags: + - 容器 + /panel/container/network/exist: + get: + description: 检查一个网络是否存在 + parameters: + - in: query + name: id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 检查网络是否存在 + tags: + - 容器 + /panel/container/network/inspect: + get: + description: 查看一个网络的详细信息 + parameters: + - in: query + name: id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 查看网络 + tags: + - 容器 + /panel/container/network/list: + get: + description: 获取所有网络列表 + parameters: + - in: query + name: limit + type: integer + - in: query + name: page + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 获取网络列表 + tags: + - 容器 + /panel/container/network/prune: + post: + description: 清理无用的网络 + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 清理网络 + tags: + - 容器 + /panel/container/network/remove: + post: + description: 删除一个网络 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/panel_app_http_requests_container.ID' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 删除网络 + tags: + - 容器 + /panel/container/prune: + post: + description: 清理无用的容器 + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 清理容器 + tags: + - 容器 + /panel/container/remove: + post: + description: 删除一个容器 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/panel_app_http_requests_container.ID' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 删除容器 + tags: + - 容器 + /panel/container/rename: + post: + description: 重命名一个容器 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/requests.ContainerRename' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 重命名容器 + tags: + - 容器 + /panel/container/restart: + post: + description: 重启一个容器 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/panel_app_http_requests_container.ID' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 重启容器 + tags: + - 容器 + /panel/container/search: + get: + description: 根据容器名称搜索容器 + parameters: + - description: 容器名称 + in: query + name: name + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 搜索容器 + tags: + - 容器 + /panel/container/start: + post: + description: 启动一个容器 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/panel_app_http_requests_container.ID' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 启动容器 + tags: + - 容器 + /panel/container/stats: + get: + description: 查看一个容器的状态信息 + parameters: + - in: query + name: id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 查看容器状态 + tags: + - 容器 + /panel/container/stop: + post: + description: 停止一个容器 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/panel_app_http_requests_container.ID' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 停止容器 + tags: + - 容器 + /panel/container/unpause: + post: + description: 取消暂停一个容器 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/panel_app_http_requests_container.ID' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 取消暂停容器 + tags: + - 容器 + /panel/container/volume/create: + post: + consumes: + - application/json + description: 创建一个卷 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/requests.VolumeCreate' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 创建卷 + tags: + - 容器 + /panel/container/volume/exist: + get: + description: 检查一个卷是否存在 + parameters: + - in: query + name: id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 检查卷是否存在 + tags: + - 容器 + /panel/container/volume/inspect: + get: + description: 查看一个卷的详细信息 + parameters: + - in: query + name: id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 查看卷 + tags: + - 容器 + /panel/container/volume/list: + get: + description: 获取所有卷列表 + parameters: + - in: query + name: limit + type: integer + - in: query + name: page + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 获取卷列表 + tags: + - 容器 + /panel/container/volume/prune: + post: + description: 清理无用的卷 + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 清理卷 + tags: + - 容器 + /panel/container/volume/remove: + post: + description: 删除一个卷 + parameters: + - description: request + in: body + name: data + required: true + schema: + $ref: '#/definitions/panel_app_http_requests_container.ID' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.SuccessResponse' + security: + - BearerToken: [] + summary: 删除卷 + tags: + - 容器 /panel/file/archive: post: consumes: diff --git a/internal/container.go b/internal/container.go index 368569f6..f43f8a34 100644 --- a/internal/container.go +++ b/internal/container.go @@ -6,6 +6,8 @@ import ( "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/volume" + + requests "panel/app/http/requests/container" ) type Container interface { @@ -27,7 +29,7 @@ type Container interface { ContainerLogs(id string) (string, error) ContainerPrune() error NetworkList() ([]types.NetworkResource, error) - NetworkCreate(name string) error + NetworkCreate(config requests.NetworkCreate) (string, error) NetworkRemove(id string) error NetworkExist(name string) (bool, error) NetworkInspect(id string) (types.NetworkResource, error) @@ -35,16 +37,16 @@ type Container interface { NetworkDisconnect(networkID string, containerID string) error NetworkPrune() error ImageList() ([]image.Summary, error) - ImageExist(reference string) (bool, error) - ImagePull(reference string) error - ImageRemove(imageID string) error + ImageExist(id string) (bool, error) + ImagePull(config requests.ImagePull) error + ImageRemove(id string) error ImagePrune() error - ImageInspect(imageID string) (types.ImageInspect, error) + ImageInspect(id string) (types.ImageInspect, error) VolumeList() ([]*volume.Volume, error) - VolumeCreate(name string, options, labels map[string]string) (volume.Volume, error) + VolumeCreate(config requests.VolumeCreate) (volume.Volume, error) VolumeExist(name string) (bool, error) - VolumeInspect(volumeID string) (volume.Volume, error) - VolumeRemove(volumeID string) error + VolumeInspect(id string) (volume.Volume, error) + VolumeRemove(id string) error VolumePrune() error SliceToMap(slice []string) map[string]string } diff --git a/internal/services/container.go b/internal/services/container.go index f2347bef..0ec7721a 100644 --- a/internal/services/container.go +++ b/internal/services/container.go @@ -2,6 +2,7 @@ package services import ( "context" + "encoding/base64" "io" "strings" @@ -10,8 +11,12 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" + "github.com/goravel/framework/support/json" + + requests "panel/app/http/requests/container" ) type Container struct { @@ -167,12 +172,37 @@ func (r *Container) NetworkList() ([]types.NetworkResource, error) { } // NetworkCreate 创建网络 -func (r *Container) NetworkCreate(name string) error { - _, err := r.client.NetworkCreate(context.Background(), name, types.NetworkCreate{ - Driver: "bridge", - }) +func (r *Container) NetworkCreate(config requests.NetworkCreate) (string, error) { + var ipamConfigs []network.IPAMConfig + if config.Ipv4.Enabled { + ipamConfigs = append(ipamConfigs, network.IPAMConfig{ + Subnet: config.Ipv4.Subnet, + Gateway: config.Ipv4.Gateway, + IPRange: config.Ipv4.IPRange, + }) + } + if config.Ipv6.Enabled { + ipamConfigs = append(ipamConfigs, network.IPAMConfig{ + Subnet: config.Ipv6.Subnet, + Gateway: config.Ipv6.Gateway, + IPRange: config.Ipv6.IPRange, + }) + } - return err + options := types.NetworkCreate{ + EnableIPv6: config.Ipv6.Enabled, + Driver: config.Driver, + Options: r.SliceToMap(config.Options), + Labels: r.SliceToMap(config.Labels), + } + if len(ipamConfigs) > 0 { + options.IPAM = &network.IPAM{ + Config: ipamConfigs, + } + } + + resp, err := r.client.NetworkCreate(context.Background(), config.Name, options) + return resp.ID, err } // NetworkRemove 删除网络 @@ -198,13 +228,13 @@ func (r *Container) NetworkInspect(id string) (types.NetworkResource, error) { } // NetworkConnect 连接网络 -func (r *Container) NetworkConnect(id string, containerID string) error { - return r.client.NetworkConnect(context.Background(), id, containerID, nil) +func (r *Container) NetworkConnect(networkID string, containerID string) error { + return r.client.NetworkConnect(context.Background(), networkID, containerID, nil) } // NetworkDisconnect 断开网络 -func (r *Container) NetworkDisconnect(id string, containerID string) error { - return r.client.NetworkDisconnect(context.Background(), id, containerID, true) +func (r *Container) NetworkDisconnect(networkID string, containerID string) error { + return r.client.NetworkDisconnect(context.Background(), networkID, containerID, true) } // NetworkPrune 清理未使用的网络 @@ -221,9 +251,9 @@ func (r *Container) ImageList() ([]image.Summary, error) { } // ImageExist 判断镜像是否存在 -func (r *Container) ImageExist(str string) (bool, error) { +func (r *Container) ImageExist(id string) (bool, error) { var options types.ImageListOptions - options.Filters = filters.NewArgs(filters.Arg("reference", str)) + options.Filters = filters.NewArgs(filters.Arg("reference", id)) images, err := r.client.ImageList(context.Background(), options) if err != nil { return false, err @@ -233,8 +263,22 @@ func (r *Container) ImageExist(str string) (bool, error) { } // ImagePull 拉取镜像 -func (r *Container) ImagePull(str string) error { - _, err := r.client.ImagePull(context.Background(), str, types.ImagePullOptions{}) +func (r *Container) ImagePull(config requests.ImagePull) error { + options := types.ImagePullOptions{} + if config.Auth { + authConfig := registry.AuthConfig{ + Username: config.Username, + Password: config.Password, + } + encodedJSON, err := json.Marshal(authConfig) + if err != nil { + return err + } + authStr := base64.URLEncoding.EncodeToString(encodedJSON) + options.RegistryAuth = authStr + } + + _, err := r.client.ImagePull(context.Background(), config.Name, options) return err } @@ -266,19 +310,19 @@ func (r *Container) VolumeList() ([]*volume.Volume, error) { } // VolumeCreate 创建存储卷 -func (r *Container) VolumeCreate(name string, options, labels map[string]string) (volume.Volume, error) { +func (r *Container) VolumeCreate(config requests.VolumeCreate) (volume.Volume, error) { return r.client.VolumeCreate(context.Background(), volume.CreateOptions{ - Name: name, - Driver: "local", - DriverOpts: options, - Labels: labels, + Name: config.Name, + Driver: config.Driver, + DriverOpts: r.SliceToMap(config.Options), + Labels: r.SliceToMap(config.Labels), }) } // VolumeExist 判断存储卷是否存在 -func (r *Container) VolumeExist(name string) (bool, error) { +func (r *Container) VolumeExist(id string) (bool, error) { var options volume.ListOptions - options.Filters = filters.NewArgs(filters.Arg("name", name)) + options.Filters = filters.NewArgs(filters.Arg("name", id)) volumes, err := r.client.VolumeList(context.Background(), options) if err != nil { return false, err @@ -312,5 +356,6 @@ func (r *Container) SliceToMap(slice []string) map[string]string { m[sps[0]] = sps[1] } } + return m } diff --git a/routes/api.go b/routes/api.go index 0ddfc86b..9f1a9c88 100644 --- a/routes/api.go +++ b/routes/api.go @@ -128,7 +128,7 @@ func Api() { r.Post("kill", containerController.ContainerKill) r.Post("rename", containerController.ContainerRename) r.Get("stats", containerController.ContainerStats) - r.Post("exist", containerController.ContainerExist) + r.Get("exist", containerController.ContainerExist) r.Get("logs", containerController.ContainerLogs) r.Post("prune", containerController.ContainerPrune) @@ -136,7 +136,7 @@ func Api() { r.Get("list", containerController.NetworkList) r.Post("create", containerController.NetworkCreate) r.Post("remove", containerController.NetworkRemove) - r.Post("exist", containerController.NetworkExist) + r.Get("exist", containerController.NetworkExist) r.Get("inspect", containerController.NetworkInspect) r.Post("connect", containerController.NetworkConnect) r.Post("disconnect", containerController.NetworkDisconnect) @@ -145,7 +145,7 @@ func Api() { r.Prefix("image").Group(func(r route.Router) { r.Get("list", containerController.ImageList) - r.Post("exist", containerController.ImageExist) + r.Get("exist", containerController.ImageExist) r.Post("pull", containerController.ImagePull) r.Post("remove", containerController.ImageRemove) r.Post("prune", containerController.ImagePrune) @@ -155,7 +155,7 @@ func Api() { r.Prefix("volume").Group(func(r route.Router) { r.Get("list", containerController.VolumeList) r.Post("create", containerController.VolumeCreate) - r.Post("exist", containerController.VolumeExist) + r.Get("exist", containerController.VolumeExist) r.Get("inspect", containerController.VolumeInspect) r.Post("remove", containerController.VolumeRemove) r.Post("prune", containerController.VolumePrune)