mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-28 05:15:04 +08:00
feat: 增加容器、镜像、网络、存储卷清理功能 (#1117)
This commit is contained in:
parent
626782102a
commit
7596099aa1
@ -179,6 +179,33 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) {
|
||||
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
|
||||
// @Summary Clean container log
|
||||
// @Description 清理容器日志
|
||||
|
@ -80,6 +80,16 @@ type ContainerOperation struct {
|
||||
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 {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
@ -51,6 +51,7 @@ type IContainerService interface {
|
||||
CreateVolume(req dto.VolumeCreat) error
|
||||
TestCompose(req dto.ComposeCreate) (bool, error)
|
||||
ComposeUpdate(req dto.ComposeUpdate) error
|
||||
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error)
|
||||
}
|
||||
|
||||
func NewIContainerService() IContainerService {
|
||||
@ -167,6 +168,51 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||
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 {
|
||||
portMap, err := checkPortStats(req.ExposedPorts)
|
||||
if err != nil {
|
||||
|
@ -24,6 +24,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
||||
baRouter.POST("/clean/log", baseApi.CleanContainerLog)
|
||||
baRouter.POST("/inspect", baseApi.Inspect)
|
||||
baRouter.POST("/operate", baseApi.ContainerOperation)
|
||||
baRouter.POST("/prune", baseApi.ContainerPrune)
|
||||
|
||||
baRouter.GET("/repo", baseApi.ListRepo)
|
||||
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": {
|
||||
"get": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"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": {
|
||||
"get": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -321,6 +321,27 @@ definitions:
|
||||
- name
|
||||
- operation
|
||||
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:
|
||||
properties:
|
||||
cache:
|
||||
@ -4516,6 +4537,35 @@ paths:
|
||||
formatEN: container [operation] [name] [newName]
|
||||
formatZH: 容器 [name] 执行 [operation] [newName]
|
||||
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:
|
||||
get:
|
||||
description: 获取镜像仓库列表
|
||||
|
@ -69,6 +69,14 @@ export namespace Container {
|
||||
id: string;
|
||||
type: string;
|
||||
}
|
||||
export interface ContainerPrune {
|
||||
pruneType: string;
|
||||
withTagAll: boolean;
|
||||
}
|
||||
export interface ContainerPruneReport {
|
||||
deletedNumber: number;
|
||||
spaceReclaimed: number;
|
||||
}
|
||||
export interface Options {
|
||||
option: string;
|
||||
}
|
||||
|
@ -20,6 +20,9 @@ export const ContainerStats = (id: string) => {
|
||||
export const ContainerOperator = (params: Container.ContainerOperate) => {
|
||||
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) => {
|
||||
return http.post<string>(`/containers/inspect`, params);
|
||||
};
|
||||
|
@ -18,7 +18,7 @@
|
||||
<el-button @click="onCancle">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button :disabled="submitInput !== submitInputInfo" @click="onConfirm">
|
||||
<el-button type="primary" :disabled="submitInput !== submitInputInfo" @click="onConfirm">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
|
@ -437,6 +437,20 @@ const message = {
|
||||
unpause: 'Unpause',
|
||||
rename: 'Rename',
|
||||
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',
|
||||
upTime: 'UpTime',
|
||||
all: 'All',
|
||||
@ -542,7 +556,7 @@ const message = {
|
||||
repoHelper: 'Does it include a mirror repository/organization/project?',
|
||||
auth: 'Auth',
|
||||
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:
|
||||
'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: '恢复',
|
||||
rename: '重命名',
|
||||
remove: '删除',
|
||||
containerPrune: '清理容器',
|
||||
containerPruneHelper: '清理容器 将删除所有处于停止状态的容器,该操作无法回滚,是否继续?',
|
||||
imagePrune: '清理镜像',
|
||||
imagePruneSome: '未标签镜像',
|
||||
imagePruneSomeHelper: '清理标签为 none 且未被任何容器使用的镜像。',
|
||||
imagePruneAll: '未使用镜像',
|
||||
imagePruneAllHelper: '清理所有未被任何容器使用的镜像。',
|
||||
networkPrune: '清理网络',
|
||||
networkPruneHelper: '清理网络 将删除所有未被使用的网络,该操作无法回滚,是否继续?',
|
||||
volumePrune: '清理存储卷',
|
||||
volumePruneHelper: '清理存储卷 将删除所有未被使用的本地存储卷,该操作无法回滚,是否继续?',
|
||||
cleanSuccess: '操作成功,本次清理数量: {0} 个!',
|
||||
cleanSuccessWithSpace: '操作成功,本次清理数量: {0} 个,释放磁盘空间: {1}!',
|
||||
container: '容器',
|
||||
upTime: '运行时长',
|
||||
all: '全部',
|
||||
|
@ -12,6 +12,9 @@
|
||||
<el-button type="primary" @click="onCreate()">
|
||||
{{ $t('container.createContainer') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="onClean()">
|
||||
{{ $t('container.containerPrune') }}
|
||||
</el-button>
|
||||
<el-button-group style="margin-left: 10px">
|
||||
<el-button :disabled="checkStatus('start')" @click="onOperate('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 Status from '@/components/status/index.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 { ElMessageBox } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
import router from '@/routers';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { computeSize } from '@/utils/util';
|
||||
|
||||
const loading = ref();
|
||||
const data = ref();
|
||||
@ -232,6 +236,34 @@ const onInspect = async (id: string) => {
|
||||
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) => {
|
||||
if (selects.value.length < 1) {
|
||||
return true;
|
||||
|
@ -19,6 +19,9 @@
|
||||
<el-button type="primary" plain @click="onOpenBuild">
|
||||
{{ $t('container.imageBuild') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="onOpenPrune()">
|
||||
{{ $t('container.imagePrune') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<TableSetting @search="search()" />
|
||||
@ -74,6 +77,7 @@
|
||||
<Load ref="dialogLoadRef" @search="search" />
|
||||
<Build ref="dialogBuildRef" @search="search" />
|
||||
<Delete ref="dialogDeleteRef" @search="search" />
|
||||
<Prune ref="dialogPruneRef" @search="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -90,6 +94,7 @@ import Save from '@/views/container/image/save/index.vue';
|
||||
import Load from '@/views/container/image/load/index.vue';
|
||||
import Build from '@/views/container/image/build/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 i18n from '@/lang';
|
||||
import router from '@/routers';
|
||||
@ -134,6 +139,7 @@ const dialogLoadRef = ref();
|
||||
const dialogSaveRef = ref();
|
||||
const dialogBuildRef = ref();
|
||||
const dialogDeleteRef = ref();
|
||||
const dialogPruneRef = ref();
|
||||
|
||||
const search = async () => {
|
||||
const repoSearch = {
|
||||
@ -162,6 +168,10 @@ const onOpenBuild = () => {
|
||||
dialogBuildRef.value!.acceptParams();
|
||||
};
|
||||
|
||||
const onOpenPrune = () => {
|
||||
dialogPruneRef.value!.acceptParams();
|
||||
};
|
||||
|
||||
const onOpenload = () => {
|
||||
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()">
|
||||
{{ $t('container.createNetwork') }}
|
||||
</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') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
@ -96,11 +99,13 @@ import CreateDialog from '@/views/container/network/create/index.vue';
|
||||
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
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 i18n from '@/lang';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
import router from '@/routers';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
const loading = ref();
|
||||
|
||||
@ -145,6 +150,29 @@ const onCreate = async () => {
|
||||
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) {
|
||||
return !row.isSystem;
|
||||
}
|
||||
|
@ -13,7 +13,10 @@
|
||||
<el-button type="primary" @click="onCreate()">
|
||||
{{ $t('container.createVolume') }}
|
||||
</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') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
@ -80,12 +83,13 @@ import TableSetting from '@/components/table-setting/index.vue';
|
||||
import CreateDialog from '@/views/container/volume/create/index.vue';
|
||||
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { deleteVolume, searchVolume, inspect, loadDockerStatus } from '@/api/modules/container';
|
||||
import { computeSize, dateFormat } from '@/utils/util';
|
||||
import { deleteVolume, searchVolume, inspect, loadDockerStatus, containerPrune } from '@/api/modules/container';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import i18n from '@/lang';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
import router from '@/routers';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
const loading = ref();
|
||||
const detailInfo = ref();
|
||||
@ -157,6 +161,34 @@ const onInspect = async (id: string) => {
|
||||
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) => {
|
||||
let names: Array<string> = [];
|
||||
if (row === null) {
|
||||
|
@ -42,7 +42,7 @@
|
||||
<el-input type="password" show-password clearable v-model="passForm.retryPassword" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="submitChangePassword(passFormRef)">
|
||||
<el-button type="primary" @click="submitChangePassword(passFormRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
Loading…
Reference in New Issue
Block a user