mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-08 22:46:51 +08:00
feat: 增加容器、镜像、网络、存储卷清理功能 (#1117)
This commit is contained in:
parent
626782102a
commit
7596099aa1
18 changed files with 511 additions and 9 deletions
|
@ -179,6 +179,33 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) {
|
||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags Container
|
||||||
|
// @Summary Clean container
|
||||||
|
// @Description 容器清理
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.ContainerPrune true "request"
|
||||||
|
// @Success 200 {object} dto.ContainerPruneReport
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /containers/prune [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["pruneType"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"清理容器 [pruneType]","formatEN":"clean container [pruneType]"}
|
||||||
|
func (b *BaseApi) ContainerPrune(c *gin.Context) {
|
||||||
|
var req dto.ContainerPrune
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
report, err := containerService.Prune(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, report)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags Container
|
// @Tags Container
|
||||||
// @Summary Clean container log
|
// @Summary Clean container log
|
||||||
// @Description 清理容器日志
|
// @Description 清理容器日志
|
||||||
|
|
|
@ -80,6 +80,16 @@ type ContainerOperation struct {
|
||||||
NewName string `json:"newName"`
|
NewName string `json:"newName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContainerPrune struct {
|
||||||
|
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network"`
|
||||||
|
WithTagAll bool `josn:"withTagAll"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerPruneReport struct {
|
||||||
|
DeletedNumber int `json:"deletedNumber"`
|
||||||
|
SpaceReclaimed int `json:"spaceReclaimed"`
|
||||||
|
}
|
||||||
|
|
||||||
type Network struct {
|
type Network struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
|
@ -51,6 +51,7 @@ type IContainerService interface {
|
||||||
CreateVolume(req dto.VolumeCreat) error
|
CreateVolume(req dto.VolumeCreat) error
|
||||||
TestCompose(req dto.ComposeCreate) (bool, error)
|
TestCompose(req dto.ComposeCreate) (bool, error)
|
||||||
ComposeUpdate(req dto.ComposeUpdate) error
|
ComposeUpdate(req dto.ComposeUpdate) error
|
||||||
|
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIContainerService() IContainerService {
|
func NewIContainerService() IContainerService {
|
||||||
|
@ -167,6 +168,51 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||||
return string(bytes), nil
|
return string(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error) {
|
||||||
|
report := dto.ContainerPruneReport{}
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return report, err
|
||||||
|
}
|
||||||
|
pruneFilters := filters.NewArgs()
|
||||||
|
if req.WithTagAll {
|
||||||
|
pruneFilters.Add("dangling", "false")
|
||||||
|
if req.PruneType != "image" {
|
||||||
|
pruneFilters.Add("until", "24h")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch req.PruneType {
|
||||||
|
case "container":
|
||||||
|
rep, err := client.ContainersPrune(context.Background(), pruneFilters)
|
||||||
|
if err != nil {
|
||||||
|
return report, err
|
||||||
|
}
|
||||||
|
report.DeletedNumber = len(rep.ContainersDeleted)
|
||||||
|
report.SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||||
|
case "image":
|
||||||
|
rep, err := client.ImagesPrune(context.Background(), pruneFilters)
|
||||||
|
if err != nil {
|
||||||
|
return report, err
|
||||||
|
}
|
||||||
|
report.DeletedNumber = len(rep.ImagesDeleted)
|
||||||
|
report.SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||||
|
case "network":
|
||||||
|
rep, err := client.NetworksPrune(context.Background(), pruneFilters)
|
||||||
|
if err != nil {
|
||||||
|
return report, err
|
||||||
|
}
|
||||||
|
report.DeletedNumber = len(rep.NetworksDeleted)
|
||||||
|
case "volume":
|
||||||
|
rep, err := client.VolumesPrune(context.Background(), pruneFilters)
|
||||||
|
if err != nil {
|
||||||
|
return report, err
|
||||||
|
}
|
||||||
|
report.DeletedNumber = len(rep.VolumesDeleted)
|
||||||
|
report.SpaceReclaimed = int(rep.SpaceReclaimed)
|
||||||
|
}
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
|
func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
|
||||||
portMap, err := checkPortStats(req.ExposedPorts)
|
portMap, err := checkPortStats(req.ExposedPorts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -24,6 +24,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
||||||
baRouter.POST("/clean/log", baseApi.CleanContainerLog)
|
baRouter.POST("/clean/log", baseApi.CleanContainerLog)
|
||||||
baRouter.POST("/inspect", baseApi.Inspect)
|
baRouter.POST("/inspect", baseApi.Inspect)
|
||||||
baRouter.POST("/operate", baseApi.ContainerOperation)
|
baRouter.POST("/operate", baseApi.ContainerOperation)
|
||||||
|
baRouter.POST("/prune", baseApi.ContainerPrune)
|
||||||
|
|
||||||
baRouter.GET("/repo", baseApi.ListRepo)
|
baRouter.GET("/repo", baseApi.ListRepo)
|
||||||
baRouter.POST("/repo/status", baseApi.CheckRepoStatus)
|
baRouter.POST("/repo/status", baseApi.CheckRepoStatus)
|
||||||
|
|
|
@ -1984,6 +1984,51 @@ var doc = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/containers/prune": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "容器清理",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Container"
|
||||||
|
],
|
||||||
|
"summary": "Clean container",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ContainerPrune"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ContainerPruneReport"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-panel-log": {
|
||||||
|
"BeforeFuntions": [],
|
||||||
|
"bodyKeys": [
|
||||||
|
"pruneType"
|
||||||
|
],
|
||||||
|
"formatEN": "clean container [pruneType]",
|
||||||
|
"formatZH": "清理容器 [pruneType]",
|
||||||
|
"paramKeys": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/containers/repo": {
|
"/containers/repo": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -10523,6 +10568,37 @@ var doc = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.ContainerPrune": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"pruneType"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"pruneType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"container",
|
||||||
|
"image",
|
||||||
|
"volume",
|
||||||
|
"network"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"withTagAll": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.ContainerPruneReport": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"deletedNumber": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"spaceReclaimed": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.ContainterStats": {
|
"dto.ContainterStats": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -1970,6 +1970,51 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/containers/prune": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "容器清理",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Container"
|
||||||
|
],
|
||||||
|
"summary": "Clean container",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ContainerPrune"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ContainerPruneReport"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-panel-log": {
|
||||||
|
"BeforeFuntions": [],
|
||||||
|
"bodyKeys": [
|
||||||
|
"pruneType"
|
||||||
|
],
|
||||||
|
"formatEN": "clean container [pruneType]",
|
||||||
|
"formatZH": "清理容器 [pruneType]",
|
||||||
|
"paramKeys": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/containers/repo": {
|
"/containers/repo": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -10509,6 +10554,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.ContainerPrune": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"pruneType"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"pruneType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"container",
|
||||||
|
"image",
|
||||||
|
"volume",
|
||||||
|
"network"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"withTagAll": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.ContainerPruneReport": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"deletedNumber": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"spaceReclaimed": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.ContainterStats": {
|
"dto.ContainterStats": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -321,6 +321,27 @@ definitions:
|
||||||
- name
|
- name
|
||||||
- operation
|
- operation
|
||||||
type: object
|
type: object
|
||||||
|
dto.ContainerPrune:
|
||||||
|
properties:
|
||||||
|
pruneType:
|
||||||
|
enum:
|
||||||
|
- container
|
||||||
|
- image
|
||||||
|
- volume
|
||||||
|
- network
|
||||||
|
type: string
|
||||||
|
withTagAll:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- pruneType
|
||||||
|
type: object
|
||||||
|
dto.ContainerPruneReport:
|
||||||
|
properties:
|
||||||
|
deletedNumber:
|
||||||
|
type: integer
|
||||||
|
spaceReclaimed:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
dto.ContainterStats:
|
dto.ContainterStats:
|
||||||
properties:
|
properties:
|
||||||
cache:
|
cache:
|
||||||
|
@ -4516,6 +4537,35 @@ paths:
|
||||||
formatEN: container [operation] [name] [newName]
|
formatEN: container [operation] [name] [newName]
|
||||||
formatZH: 容器 [name] 执行 [operation] [newName]
|
formatZH: 容器 [name] 执行 [operation] [newName]
|
||||||
paramKeys: []
|
paramKeys: []
|
||||||
|
/containers/prune:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 容器清理
|
||||||
|
parameters:
|
||||||
|
- description: request
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.ContainerPrune'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.ContainerPruneReport'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Clean container
|
||||||
|
tags:
|
||||||
|
- Container
|
||||||
|
x-panel-log:
|
||||||
|
BeforeFuntions: []
|
||||||
|
bodyKeys:
|
||||||
|
- pruneType
|
||||||
|
formatEN: clean container [pruneType]
|
||||||
|
formatZH: 清理容器 [pruneType]
|
||||||
|
paramKeys: []
|
||||||
/containers/repo:
|
/containers/repo:
|
||||||
get:
|
get:
|
||||||
description: 获取镜像仓库列表
|
description: 获取镜像仓库列表
|
||||||
|
|
|
@ -69,6 +69,14 @@ export namespace Container {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
export interface ContainerPrune {
|
||||||
|
pruneType: string;
|
||||||
|
withTagAll: boolean;
|
||||||
|
}
|
||||||
|
export interface ContainerPruneReport {
|
||||||
|
deletedNumber: number;
|
||||||
|
spaceReclaimed: number;
|
||||||
|
}
|
||||||
export interface Options {
|
export interface Options {
|
||||||
option: string;
|
option: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ export const ContainerStats = (id: string) => {
|
||||||
export const ContainerOperator = (params: Container.ContainerOperate) => {
|
export const ContainerOperator = (params: Container.ContainerOperate) => {
|
||||||
return http.post(`/containers/operate`, params);
|
return http.post(`/containers/operate`, params);
|
||||||
};
|
};
|
||||||
|
export const containerPrune = (params: Container.ContainerPrune) => {
|
||||||
|
return http.post<Container.ContainerPruneReport>(`/containers/prune`, params);
|
||||||
|
};
|
||||||
export const inspect = (params: Container.ContainerInspect) => {
|
export const inspect = (params: Container.ContainerInspect) => {
|
||||||
return http.post<string>(`/containers/inspect`, params);
|
return http.post<string>(`/containers/inspect`, params);
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<el-button @click="onCancle">
|
<el-button @click="onCancle">
|
||||||
{{ $t('commons.button.cancel') }}
|
{{ $t('commons.button.cancel') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button :disabled="submitInput !== submitInputInfo" @click="onConfirm">
|
<el-button type="primary" :disabled="submitInput !== submitInputInfo" @click="onConfirm">
|
||||||
{{ $t('commons.button.confirm') }}
|
{{ $t('commons.button.confirm') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -437,6 +437,20 @@ const message = {
|
||||||
unpause: 'Unpause',
|
unpause: 'Unpause',
|
||||||
rename: 'Rename',
|
rename: 'Rename',
|
||||||
remove: 'Remove',
|
remove: 'Remove',
|
||||||
|
containerPrune: 'Container prune',
|
||||||
|
containerPruneHelper: 'Remove all stopped containers. Do you want to continue?',
|
||||||
|
imagePrune: 'Image prune',
|
||||||
|
imagePruneSome: 'Clean unlabeled',
|
||||||
|
imagePruneSomeHelper: 'Remove all unused and unlabeled container images。',
|
||||||
|
imagePruneAll: 'Clean unused',
|
||||||
|
imagePruneAllHelper: 'Remove all unused images, not just unlabeled',
|
||||||
|
networkPrune: 'Network prune',
|
||||||
|
networkPruneHelper: 'Remove all unused networks. Do you want to continue?',
|
||||||
|
volumePrune: 'Volue prune',
|
||||||
|
volumePruneHelper: 'Remove all unused local volumes. Do you want to continue?',
|
||||||
|
cleanSuccess: 'The operation is successful, the number of this cleanup: {0}!',
|
||||||
|
cleanSuccessWithSpace:
|
||||||
|
'The operation is successful. The number of disks cleared this time is {0}. The disk space freed is {1}!',
|
||||||
container: 'Container',
|
container: 'Container',
|
||||||
upTime: 'UpTime',
|
upTime: 'UpTime',
|
||||||
all: 'All',
|
all: 'All',
|
||||||
|
@ -542,7 +556,7 @@ const message = {
|
||||||
repoHelper: 'Does it include a mirror repository/organization/project?',
|
repoHelper: 'Does it include a mirror repository/organization/project?',
|
||||||
auth: 'Auth',
|
auth: 'Auth',
|
||||||
mirrorHelper:
|
mirrorHelper:
|
||||||
'If there are multiple mirrors, newlines must be displayed, for example:\nhttps://hub-mirror.c.163.com \nhttps://reg-mirror.qiniu.com',
|
'If there are multiple mirrors, newlines must be displayed, for example:\nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com',
|
||||||
registrieHelper:
|
registrieHelper:
|
||||||
'If multiple private repositories exist, newlines must be displayed, for example:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
'If multiple private repositories exist, newlines must be displayed, for example:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
||||||
|
|
||||||
|
|
|
@ -456,6 +456,19 @@ const message = {
|
||||||
unpause: '恢复',
|
unpause: '恢复',
|
||||||
rename: '重命名',
|
rename: '重命名',
|
||||||
remove: '删除',
|
remove: '删除',
|
||||||
|
containerPrune: '清理容器',
|
||||||
|
containerPruneHelper: '清理容器 将删除所有处于停止状态的容器,该操作无法回滚,是否继续?',
|
||||||
|
imagePrune: '清理镜像',
|
||||||
|
imagePruneSome: '未标签镜像',
|
||||||
|
imagePruneSomeHelper: '清理标签为 none 且未被任何容器使用的镜像。',
|
||||||
|
imagePruneAll: '未使用镜像',
|
||||||
|
imagePruneAllHelper: '清理所有未被任何容器使用的镜像。',
|
||||||
|
networkPrune: '清理网络',
|
||||||
|
networkPruneHelper: '清理网络 将删除所有未被使用的网络,该操作无法回滚,是否继续?',
|
||||||
|
volumePrune: '清理存储卷',
|
||||||
|
volumePruneHelper: '清理存储卷 将删除所有未被使用的本地存储卷,该操作无法回滚,是否继续?',
|
||||||
|
cleanSuccess: '操作成功,本次清理数量: {0} 个!',
|
||||||
|
cleanSuccessWithSpace: '操作成功,本次清理数量: {0} 个,释放磁盘空间: {1}!',
|
||||||
container: '容器',
|
container: '容器',
|
||||||
upTime: '运行时长',
|
upTime: '运行时长',
|
||||||
all: '全部',
|
all: '全部',
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
<el-button type="primary" @click="onCreate()">
|
<el-button type="primary" @click="onCreate()">
|
||||||
{{ $t('container.createContainer') }}
|
{{ $t('container.createContainer') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button type="primary" plain @click="onClean()">
|
||||||
|
{{ $t('container.containerPrune') }}
|
||||||
|
</el-button>
|
||||||
<el-button-group style="margin-left: 10px">
|
<el-button-group style="margin-left: 10px">
|
||||||
<el-button :disabled="checkStatus('start')" @click="onOperate('start')">
|
<el-button :disabled="checkStatus('start')" @click="onOperate('start')">
|
||||||
{{ $t('container.start') }}
|
{{ $t('container.start') }}
|
||||||
|
@ -137,12 +140,13 @@ import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
||||||
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
|
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
|
||||||
import Status from '@/components/status/index.vue';
|
import Status from '@/components/status/index.vue';
|
||||||
import { reactive, onMounted, ref } from 'vue';
|
import { reactive, onMounted, ref } from 'vue';
|
||||||
import { ContainerOperator, inspect, loadDockerStatus, searchContainer } from '@/api/modules/container';
|
import { ContainerOperator, containerPrune, inspect, loadDockerStatus, searchContainer } from '@/api/modules/container';
|
||||||
import { Container } from '@/api/interface/container';
|
import { Container } from '@/api/interface/container';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { computeSize } from '@/utils/util';
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const data = ref();
|
const data = ref();
|
||||||
|
@ -232,6 +236,34 @@ const onInspect = async (id: string) => {
|
||||||
mydetail.value!.acceptParams(param);
|
mydetail.value!.acceptParams(param);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onClean = () => {
|
||||||
|
ElMessageBox.confirm(i18n.global.t('container.containerPruneHelper'), i18n.global.t('container.containerPrune'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
}).then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
let params = {
|
||||||
|
pruneType: 'container',
|
||||||
|
withTagAll: false,
|
||||||
|
};
|
||||||
|
await containerPrune(params)
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(
|
||||||
|
i18n.global.t('container.cleanSuccessWithSpace', [
|
||||||
|
res.data.deletedNumber,
|
||||||
|
computeSize(res.data.spaceReclaimed),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const checkStatus = (operation: string) => {
|
const checkStatus = (operation: string) => {
|
||||||
if (selects.value.length < 1) {
|
if (selects.value.length < 1) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
<el-button type="primary" plain @click="onOpenBuild">
|
<el-button type="primary" plain @click="onOpenBuild">
|
||||||
{{ $t('container.imageBuild') }}
|
{{ $t('container.imageBuild') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button type="primary" plain @click="onOpenPrune()">
|
||||||
|
{{ $t('container.imagePrune') }}
|
||||||
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<TableSetting @search="search()" />
|
<TableSetting @search="search()" />
|
||||||
|
@ -74,6 +77,7 @@
|
||||||
<Load ref="dialogLoadRef" @search="search" />
|
<Load ref="dialogLoadRef" @search="search" />
|
||||||
<Build ref="dialogBuildRef" @search="search" />
|
<Build ref="dialogBuildRef" @search="search" />
|
||||||
<Delete ref="dialogDeleteRef" @search="search" />
|
<Delete ref="dialogDeleteRef" @search="search" />
|
||||||
|
<Prune ref="dialogPruneRef" @search="search" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -90,6 +94,7 @@ import Save from '@/views/container/image/save/index.vue';
|
||||||
import Load from '@/views/container/image/load/index.vue';
|
import Load from '@/views/container/image/load/index.vue';
|
||||||
import Build from '@/views/container/image/build/index.vue';
|
import Build from '@/views/container/image/build/index.vue';
|
||||||
import Delete from '@/views/container/image/delete/index.vue';
|
import Delete from '@/views/container/image/delete/index.vue';
|
||||||
|
import Prune from '@/views/container/image/prune/index.vue';
|
||||||
import { searchImage, listImageRepo, loadDockerStatus, imageRemove } from '@/api/modules/container';
|
import { searchImage, listImageRepo, loadDockerStatus, imageRemove } from '@/api/modules/container';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
|
@ -134,6 +139,7 @@ const dialogLoadRef = ref();
|
||||||
const dialogSaveRef = ref();
|
const dialogSaveRef = ref();
|
||||||
const dialogBuildRef = ref();
|
const dialogBuildRef = ref();
|
||||||
const dialogDeleteRef = ref();
|
const dialogDeleteRef = ref();
|
||||||
|
const dialogPruneRef = ref();
|
||||||
|
|
||||||
const search = async () => {
|
const search = async () => {
|
||||||
const repoSearch = {
|
const repoSearch = {
|
||||||
|
@ -162,6 +168,10 @@ const onOpenBuild = () => {
|
||||||
dialogBuildRef.value!.acceptParams();
|
dialogBuildRef.value!.acceptParams();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onOpenPrune = () => {
|
||||||
|
dialogPruneRef.value!.acceptParams();
|
||||||
|
};
|
||||||
|
|
||||||
const onOpenload = () => {
|
const onOpenload = () => {
|
||||||
dialogLoadRef.value!.acceptParams();
|
dialogLoadRef.value!.acceptParams();
|
||||||
};
|
};
|
||||||
|
|
76
frontend/src/views/container/image/prune/index.vue
Normal file
76
frontend/src/views/container/image/prune/index.vue
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog v-model="dialogVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="30%">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>{{ $t('container.imagePrune') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="deleteForm" v-loading="loading">
|
||||||
|
<el-form-item>
|
||||||
|
<el-radio-group v-model="withTagAll">
|
||||||
|
<el-radio :label="false">{{ $t('container.imagePruneSome') }}</el-radio>
|
||||||
|
<el-radio :label="true">{{ $t('container.imagePruneAll') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<span class="input-help">
|
||||||
|
{{ withTagAll ? $t('container.imagePruneAllHelper') : $t('container.imagePruneSomeHelper') }}
|
||||||
|
</span>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisiable = false">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="onClean" :disabled="loading">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { containerPrune } from '@/api/modules/container';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { computeSize } from '@/utils/util';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const dialogVisiable = ref(false);
|
||||||
|
const withTagAll = ref(false);
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const acceptParams = (): void => {
|
||||||
|
dialogVisiable.value = true;
|
||||||
|
withTagAll.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
const onClean = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
let params = {
|
||||||
|
pruneType: 'image',
|
||||||
|
withTagAll: withTagAll.value,
|
||||||
|
};
|
||||||
|
await containerPrune(params)
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
dialogVisiable.value = false;
|
||||||
|
MsgSuccess(
|
||||||
|
i18n.global.t('container.cleanSuccessWithSpace', [
|
||||||
|
res.data.deletedNumber,
|
||||||
|
computeSize(res.data.spaceReclaimed),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
emit('search');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -13,7 +13,10 @@
|
||||||
<el-button type="primary" @click="onCreate()">
|
<el-button type="primary" @click="onCreate()">
|
||||||
{{ $t('container.createNetwork') }}
|
{{ $t('container.createNetwork') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" plain :disabled="selects.length === 0" @click="batchDelete(null)">
|
<el-button type="primary" plain @click="onClean()">
|
||||||
|
{{ $t('container.networkPrune') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="selects.length === 0" @click="batchDelete(null)">
|
||||||
{{ $t('commons.button.delete') }}
|
{{ $t('commons.button.delete') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
@ -96,11 +99,13 @@ import CreateDialog from '@/views/container/network/create/index.vue';
|
||||||
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
|
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
|
||||||
import { reactive, onMounted, ref } from 'vue';
|
import { reactive, onMounted, ref } from 'vue';
|
||||||
import { dateFormat } from '@/utils/util';
|
import { dateFormat } from '@/utils/util';
|
||||||
import { deleteNetwork, searchNetwork, inspect, loadDockerStatus } from '@/api/modules/container';
|
import { deleteNetwork, searchNetwork, inspect, loadDockerStatus, containerPrune } from '@/api/modules/container';
|
||||||
import { Container } from '@/api/interface/container';
|
import { Container } from '@/api/interface/container';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
|
import { ElMessageBox } from 'element-plus';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
|
|
||||||
|
@ -145,6 +150,29 @@ const onCreate = async () => {
|
||||||
dialogCreateRef.value!.acceptParams();
|
dialogCreateRef.value!.acceptParams();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onClean = () => {
|
||||||
|
ElMessageBox.confirm(i18n.global.t('container.networkPruneHelper'), i18n.global.t('container.networkPrune'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
}).then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
let params = {
|
||||||
|
pruneType: 'network',
|
||||||
|
withTagAll: false,
|
||||||
|
};
|
||||||
|
await containerPrune(params)
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('container.cleanSuccess', [res.data.deletedNumber]));
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function selectable(row) {
|
function selectable(row) {
|
||||||
return !row.isSystem;
|
return !row.isSystem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,10 @@
|
||||||
<el-button type="primary" @click="onCreate()">
|
<el-button type="primary" @click="onCreate()">
|
||||||
{{ $t('container.createVolume') }}
|
{{ $t('container.createVolume') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" plain :disabled="selects.length === 0" @click="batchDelete(null)">
|
<el-button type="primary" plain @click="onClean()">
|
||||||
|
{{ $t('container.volumePrune') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="selects.length === 0" @click="batchDelete(null)">
|
||||||
{{ $t('commons.button.delete') }}
|
{{ $t('commons.button.delete') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
@ -80,12 +83,13 @@ import TableSetting from '@/components/table-setting/index.vue';
|
||||||
import CreateDialog from '@/views/container/volume/create/index.vue';
|
import CreateDialog from '@/views/container/volume/create/index.vue';
|
||||||
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
|
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
|
||||||
import { reactive, onMounted, ref } from 'vue';
|
import { reactive, onMounted, ref } from 'vue';
|
||||||
import { dateFormat } from '@/utils/util';
|
import { computeSize, dateFormat } from '@/utils/util';
|
||||||
import { deleteVolume, searchVolume, inspect, loadDockerStatus } from '@/api/modules/container';
|
import { deleteVolume, searchVolume, inspect, loadDockerStatus, containerPrune } from '@/api/modules/container';
|
||||||
import { Container } from '@/api/interface/container';
|
import { Container } from '@/api/interface/container';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const detailInfo = ref();
|
const detailInfo = ref();
|
||||||
|
@ -157,6 +161,34 @@ const onInspect = async (id: string) => {
|
||||||
codemirror.value!.acceptParams(param);
|
codemirror.value!.acceptParams(param);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onClean = () => {
|
||||||
|
ElMessageBox.confirm(i18n.global.t('container.volumePruneHelper'), i18n.global.t('container.volumePrune'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
}).then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
let params = {
|
||||||
|
pruneType: 'volume',
|
||||||
|
withTagAll: false,
|
||||||
|
};
|
||||||
|
await containerPrune(params)
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(
|
||||||
|
i18n.global.t('container.cleanSuccessWithSpace', [
|
||||||
|
res.data.deletedNumber,
|
||||||
|
computeSize(res.data.spaceReclaimed),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const batchDelete = async (row: Container.VolumeInfo | null) => {
|
const batchDelete = async (row: Container.VolumeInfo | null) => {
|
||||||
let names: Array<string> = [];
|
let names: Array<string> = [];
|
||||||
if (row === null) {
|
if (row === null) {
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
<el-input type="password" show-password clearable v-model="passForm.retryPassword" />
|
<el-input type="password" show-password clearable v-model="passForm.retryPassword" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="submitChangePassword(passFormRef)">
|
<el-button type="primary" @click="submitChangePassword(passFormRef)">
|
||||||
{{ $t('commons.button.confirm') }}
|
{{ $t('commons.button.confirm') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
Loading…
Add table
Reference in a new issue