mirror of
https://github.com/cuigh/swirl
synced 2024-12-27 22:32:49 +00:00
Add prune function to image and container
This commit is contained in:
parent
70837391d1
commit
bb48beec82
@ -18,6 +18,7 @@ type ContainerHandler struct {
|
||||
Delete web.HandlerFunc `path:"/delete" method:"post" auth:"container.delete" desc:"delete container"`
|
||||
FetchLogs web.HandlerFunc `path:"/fetch-logs" auth:"container.logs" desc:"fetch logs of container"`
|
||||
Connect web.HandlerFunc `path:"/connect" auth:"container.execute" desc:"connect to a running container"`
|
||||
Prune web.HandlerFunc `path:"/prune" method:"post" auth:"container.delete" desc:"delete unused containers"`
|
||||
}
|
||||
|
||||
// NewContainer creates an instance of ContainerHandler
|
||||
@ -28,6 +29,7 @@ func NewContainer(b biz.ContainerBiz) *ContainerHandler {
|
||||
Delete: containerDelete(b),
|
||||
FetchLogs: containerFetchLogs(b),
|
||||
Connect: containerConnect(b),
|
||||
Prune: containerPrune(b),
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,11 +80,12 @@ func containerDelete(b biz.ContainerBiz) web.HandlerFunc {
|
||||
type Args struct {
|
||||
Node string `json:"node"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
return func(ctx web.Context) (err error) {
|
||||
args := &Args{}
|
||||
if err = ctx.Bind(args); err == nil {
|
||||
err = b.Delete(args.Node, args.ID, ctx.User())
|
||||
err = b.Delete(args.Node, args.ID, args.Name, ctx.User())
|
||||
}
|
||||
return ajax(ctx, err)
|
||||
}
|
||||
@ -214,3 +217,25 @@ func containerConnect(b biz.ContainerBiz) web.HandlerFunc {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func containerPrune(b biz.ContainerBiz) web.HandlerFunc {
|
||||
type Args struct {
|
||||
Node string `json:"node"`
|
||||
}
|
||||
return func(ctx web.Context) (err error) {
|
||||
args := &Args{}
|
||||
if err = ctx.Bind(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count, size, err := b.Prune(args.Node, ctx.User())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return success(ctx, data.Map{
|
||||
"count": count,
|
||||
"size": size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
24
api/image.go
24
api/image.go
@ -11,6 +11,7 @@ type ImageHandler struct {
|
||||
Search web.HandlerFunc `path:"/search" auth:"image.view" desc:"search images"`
|
||||
Find web.HandlerFunc `path:"/find" auth:"image.view" desc:"find image by id"`
|
||||
Delete web.HandlerFunc `path:"/delete" method:"post" auth:"image.delete" desc:"delete image"`
|
||||
Prune web.HandlerFunc `path:"/prune" method:"post" auth:"image.delete" desc:"delete unused images"`
|
||||
}
|
||||
|
||||
// NewImage creates an instance of ImageHandler
|
||||
@ -19,6 +20,7 @@ func NewImage(b biz.ImageBiz) *ImageHandler {
|
||||
Search: imageSearch(b),
|
||||
Find: imageFind(b),
|
||||
Delete: imageDelete(b),
|
||||
Prune: imagePrune(b),
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,3 +79,25 @@ func imageDelete(b biz.ImageBiz) web.HandlerFunc {
|
||||
return ajax(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
func imagePrune(b biz.ImageBiz) web.HandlerFunc {
|
||||
type Args struct {
|
||||
Node string `json:"node"`
|
||||
}
|
||||
return func(ctx web.Context) (err error) {
|
||||
args := &Args{}
|
||||
if err = ctx.Bind(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count, size, err := b.Prune(args.Node, ctx.User())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return success(ctx, data.Map{
|
||||
"count": count,
|
||||
"size": size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -103,14 +103,14 @@ func volumePrune(b biz.VolumeBiz) web.HandlerFunc {
|
||||
return err
|
||||
}
|
||||
|
||||
deletedVolumes, reclaimedSpace, err := b.Prune(args.Node, ctx.User())
|
||||
count, size, err := b.Prune(args.Node, ctx.User())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return success(ctx, data.Map{
|
||||
"deletedVolumes": deletedVolumes,
|
||||
"reclaimedSpace": reclaimedSpace,
|
||||
"count": count,
|
||||
"size": size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -14,19 +14,21 @@ import (
|
||||
type ContainerBiz interface {
|
||||
Search(node, name, status string, pageIndex, pageSize int) ([]*Container, int, error)
|
||||
Find(node, id string) (container *Container, raw string, err error)
|
||||
Delete(node, id string, user web.User) (err error)
|
||||
Delete(node, id, name string, user web.User) (err error)
|
||||
FetchLogs(node, id string, lines int, timestamps bool) (stdout, stderr string, err error)
|
||||
ExecCreate(node, id string, cmd string) (resp types.IDResponse, err error)
|
||||
ExecAttach(node, id string) (resp types.HijackedResponse, err error)
|
||||
ExecStart(node, id string) error
|
||||
Prune(node string, user web.User) (count int, size uint64, err error)
|
||||
}
|
||||
|
||||
func NewContainer(d *docker.Docker) ContainerBiz {
|
||||
return &containerBiz{d: d}
|
||||
func NewContainer(d *docker.Docker, eb EventBiz) ContainerBiz {
|
||||
return &containerBiz{d: d, eb: eb}
|
||||
}
|
||||
|
||||
type containerBiz struct {
|
||||
d *docker.Docker
|
||||
d *docker.Docker
|
||||
eb EventBiz
|
||||
}
|
||||
|
||||
func (b *containerBiz) Find(node, id string) (c *Container, raw string, err error) {
|
||||
@ -58,11 +60,11 @@ func (b *containerBiz) Search(node, name, status string, pageIndex, pageSize int
|
||||
return containers, total, nil
|
||||
}
|
||||
|
||||
func (b *containerBiz) Delete(node, id string, user web.User) (err error) {
|
||||
func (b *containerBiz) Delete(node, id, name string, user web.User) (err error) {
|
||||
err = b.d.ContainerRemove(context.TODO(), node, id)
|
||||
//if err == nil {
|
||||
// Event.CreateContainer(model.EventActionDelete, id, user)
|
||||
//}
|
||||
if err == nil {
|
||||
b.eb.CreateContainer(EventActionDelete, id, name, user)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -86,6 +88,15 @@ func (b *containerBiz) FetchLogs(node, id string, lines int, timestamps bool) (s
|
||||
return stdout.String(), stderr.String(), nil
|
||||
}
|
||||
|
||||
func (b *containerBiz) Prune(node string, user web.User) (count int, size uint64, err error) {
|
||||
var report types.ContainersPruneReport
|
||||
if report, err = b.d.ContainerPrune(context.TODO(), node); err == nil {
|
||||
count, size = len(report.ContainersDeleted), report.SpaceReclaimed
|
||||
b.eb.CreateContainer(EventActionPrune, "", "", user)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
12
biz/event.go
12
biz/event.go
@ -21,6 +21,8 @@ const (
|
||||
EventTypeStack EventType = "Stack"
|
||||
EventTypeConfig EventType = "Config"
|
||||
EventTypeSecret EventType = "Secret"
|
||||
EventTypeImage EventType = "Image"
|
||||
EventTypeContainer EventType = "Container"
|
||||
EventTypeVolume EventType = "Volume"
|
||||
EventTypeUser EventType = "User"
|
||||
EventTypeRole EventType = "Role"
|
||||
@ -54,6 +56,8 @@ type EventBiz interface {
|
||||
CreateConfig(action EventAction, id, name string, user web.User)
|
||||
CreateSecret(action EventAction, id, name string, user web.User)
|
||||
CreateStack(action EventAction, name string, user web.User)
|
||||
CreateImage(action EventAction, id string, user web.User)
|
||||
CreateContainer(action EventAction, id, name string, user web.User)
|
||||
CreateVolume(action EventAction, name string, user web.User)
|
||||
CreateUser(action EventAction, id, name string, user web.User)
|
||||
CreateRole(action EventAction, id, name string, user web.User)
|
||||
@ -110,6 +114,14 @@ func (b *eventBiz) CreateNode(action EventAction, id, name string, user web.User
|
||||
b.create(EventTypeNode, action, id, name, user)
|
||||
}
|
||||
|
||||
func (b *eventBiz) CreateImage(action EventAction, id string, user web.User) {
|
||||
b.create(EventTypeImage, action, id, "", user)
|
||||
}
|
||||
|
||||
func (b *eventBiz) CreateContainer(action EventAction, id, name string, user web.User) {
|
||||
b.create(EventTypeContainer, action, id, name, user)
|
||||
}
|
||||
|
||||
func (b *eventBiz) CreateVolume(action EventAction, name string, user web.User) {
|
||||
b.create(EventTypeVolume, action, name, name, user)
|
||||
}
|
||||
|
23
biz/image.go
23
biz/image.go
@ -15,14 +15,16 @@ type ImageBiz interface {
|
||||
Search(node, name string, pageIndex, pageSize int) ([]*Image, int, error)
|
||||
Find(node, name string) (image *Image, raw string, err error)
|
||||
Delete(node, id string, user web.User) (err error)
|
||||
Prune(node string, user web.User) (count int, size uint64, err error)
|
||||
}
|
||||
|
||||
func NewImage(d *docker.Docker) ImageBiz {
|
||||
return &imageBiz{d: d}
|
||||
func NewImage(d *docker.Docker, eb EventBiz) ImageBiz {
|
||||
return &imageBiz{d: d, eb: eb}
|
||||
}
|
||||
|
||||
type imageBiz struct {
|
||||
d *docker.Docker
|
||||
d *docker.Docker
|
||||
eb EventBiz
|
||||
}
|
||||
|
||||
func (b *imageBiz) Find(node, id string) (img *Image, raw string, err error) {
|
||||
@ -62,9 +64,18 @@ func (b *imageBiz) Search(node, name string, pageIndex, pageSize int) (images []
|
||||
|
||||
func (b *imageBiz) Delete(node, id string, user web.User) (err error) {
|
||||
err = b.d.ImageRemove(context.TODO(), node, id)
|
||||
//if err == nil {
|
||||
// Event.CreateImage(model.EventActionDelete, id, user)
|
||||
//}
|
||||
if err == nil {
|
||||
b.eb.CreateImage(EventActionDelete, id, user)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *imageBiz) Prune(node string, user web.User) (count int, size uint64, err error) {
|
||||
var report types.ImagesPruneReport
|
||||
if report, err = b.d.ImagePrune(context.TODO(), node); err == nil {
|
||||
count, size = len(report.ImagesDeleted), report.SpaceReclaimed
|
||||
b.eb.CreateImage(EventActionPrune, "", user)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ type VolumeBiz interface {
|
||||
Find(node, name string) (volume *Volume, raw string, err error)
|
||||
Delete(node, name string, user web.User) (err error)
|
||||
Create(volume *Volume, user web.User) (err error)
|
||||
Prune(node string, user web.User) (deletedVolumes []string, reclaimedSpace uint64, err error)
|
||||
Prune(node string, user web.User) (count int, size uint64, err error)
|
||||
}
|
||||
|
||||
func NewVolume(d *docker.Docker, eb EventBiz) VolumeBiz {
|
||||
@ -85,11 +85,11 @@ func (b *volumeBiz) Create(vol *Volume, user web.User) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (b *volumeBiz) Prune(node string, user web.User) (deletedVolumes []string, reclaimedSpace uint64, err error) {
|
||||
func (b *volumeBiz) Prune(node string, user web.User) (count int, size uint64, err error) {
|
||||
var report types.VolumesPruneReport
|
||||
report, err = b.d.VolumePrune(context.TODO(), node)
|
||||
if err == nil {
|
||||
deletedVolumes, reclaimedSpace = report.VolumesDeleted, report.SpaceReclaimed
|
||||
count, size = len(report.VolumesDeleted), report.SpaceReclaimed
|
||||
b.eb.CreateVolume(EventActionPrune, "", user)
|
||||
}
|
||||
return
|
||||
|
@ -136,3 +136,12 @@ func (d *Docker) ContainerLogs(ctx context.Context, node, id string, lines int,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ContainerPrune remove all unused containers.
|
||||
func (d *Docker) ContainerPrune(ctx context.Context, node string) (report types.ContainersPruneReport, err error) {
|
||||
var c *client.Client
|
||||
if c, err = d.agent(node); err == nil {
|
||||
report, err = c.ContainersPrune(ctx, filters.NewArgs())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -59,3 +59,12 @@ func (d *Docker) ImageRemove(ctx context.Context, node, id string) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ImagePrune remove all unused images.
|
||||
func (d *Docker) ImagePrune(ctx context.Context, node string) (report types.ImagesPruneReport, err error) {
|
||||
var c *client.Client
|
||||
if c, err = d.agent(node); err == nil {
|
||||
report, err = c.ImagesPrune(ctx, filters.NewArgs(filters.Arg("dangling", "false")))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -79,6 +79,13 @@ export class ContainerApi {
|
||||
stderr: string;
|
||||
}>('/container/fetch-logs', args)
|
||||
}
|
||||
|
||||
prune(node: string) {
|
||||
return ajax.post<{
|
||||
count: number;
|
||||
size: number;
|
||||
}>('/container/prune', { node })
|
||||
}
|
||||
}
|
||||
|
||||
export default new ContainerApi
|
||||
|
@ -74,6 +74,13 @@ export class ImageApi {
|
||||
delete(node: string, id: string, name: string) {
|
||||
return ajax.post<Result<Object>>('/image/delete', { node, id, name })
|
||||
}
|
||||
|
||||
prune(node: string) {
|
||||
return ajax.post<{
|
||||
count: number;
|
||||
size: number;
|
||||
}>('/image/prune', { node })
|
||||
}
|
||||
}
|
||||
|
||||
export default new ImageApi
|
||||
|
@ -36,11 +36,6 @@ export interface FindResult {
|
||||
raw: string;
|
||||
}
|
||||
|
||||
export interface PruneResult {
|
||||
deletedVolumes: string[];
|
||||
reclaimedSpace: number;
|
||||
}
|
||||
|
||||
export class VolumeApi {
|
||||
find(node: string, name: string) {
|
||||
return ajax.get<FindResult>('/volume/find', { node, name })
|
||||
@ -59,7 +54,10 @@ export class VolumeApi {
|
||||
}
|
||||
|
||||
prune(node: string) {
|
||||
return ajax.post<PruneResult>('/volume/prune', { node })
|
||||
return ajax.post<{
|
||||
count: number;
|
||||
size: number;
|
||||
}>('/volume/prune', { node })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,6 +233,14 @@ export default {
|
||||
"export_chart": {
|
||||
"title": "Export chart",
|
||||
},
|
||||
"prune_image": {
|
||||
"title": "Prune image",
|
||||
"body": "Are you sure you want to clean up unused images?",
|
||||
},
|
||||
"prune_container": {
|
||||
"title": "Prune container",
|
||||
"body": "Are you sure you want to clean up stopped containers?",
|
||||
},
|
||||
"prune_volume": {
|
||||
"title": "Prune volume",
|
||||
"body": "Are you sure you want to clean up unused data volumes?",
|
||||
@ -384,6 +392,8 @@ export default {
|
||||
"service_notice": "This service belong to '%s' stack, usually you should modify original compose instead of updating it directly.",
|
||||
"password_notice": "You are a LDAP user, can not modify password here.",
|
||||
"setting_notice": "You must restart Swirl to activate modifications.",
|
||||
"prune_image_success": "A total of {count} images are cleaned up to free up {size} of space.",
|
||||
"prune_container_success": "A total of {count} containers are cleaned up to free up {size} of space.",
|
||||
"prune_volume_success": "A total of {count} data volumes are cleaned up to free up {size} of space.",
|
||||
"403": "Sorry, you don't have permission to access this page",
|
||||
"404": "The page you are requesting has been moved, deleted, renamed, or may not exist",
|
||||
|
@ -233,6 +233,14 @@ export default {
|
||||
"export_chart": {
|
||||
"title": "导出图表",
|
||||
},
|
||||
"prune_image": {
|
||||
"title": "清理镜像",
|
||||
"body": "是否确实要清理未使用的镜像?",
|
||||
},
|
||||
"prune_container": {
|
||||
"title": "清理容器",
|
||||
"body": "是否确实要清理已停止的容器?",
|
||||
},
|
||||
"prune_volume": {
|
||||
"title": "清理数据卷",
|
||||
"body": "是否确实要清理未使用的数据卷?",
|
||||
@ -384,6 +392,8 @@ export default {
|
||||
"service_notice": "这个服务属于编排 {stack},通常你应该修改原始的编排而不是直接修改此服务信息,否则后续重新部署编排时这些修改将会丢失。",
|
||||
"password_notice": "你是 LDAP 用户,不能在这里修改密码。",
|
||||
"setting_notice": "你必须重启 Swirl 才能让设置生效。",
|
||||
"prune_image_success": "共清理 {count} 个镜像,释放 {size} 空间。",
|
||||
"prune_container_success": "共清理 {count} 个容器,释放 {size} 空间。",
|
||||
"prune_volume_success": "共清理 {count} 个数据卷,释放 {size} 空间。",
|
||||
"403": "很抱歉,你没有权限访问此页面",
|
||||
"404": "您要请求的页面已被移动、删除、重命名或可能不存在",
|
||||
|
@ -1,5 +1,16 @@
|
||||
<template>
|
||||
<x-page-header />
|
||||
<x-page-header>
|
||||
<template #action>
|
||||
<n-button secondary size="small" type="warning" @click="prune">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<close-icon />
|
||||
</n-icon>
|
||||
</template>
|
||||
{{ t('buttons.prune') }}
|
||||
</n-button>
|
||||
</template>
|
||||
</x-page-header>
|
||||
<n-space class="page-body" vertical :size="12">
|
||||
<n-space :size="12">
|
||||
<n-select
|
||||
@ -38,13 +49,15 @@ import {
|
||||
NDataTable,
|
||||
NInput,
|
||||
NSelect,
|
||||
NIcon,
|
||||
} from "naive-ui";
|
||||
import { CloseOutline as CloseIcon } from "@vicons/ionicons5";
|
||||
import XPageHeader from "@/components/PageHeader.vue";
|
||||
import containerApi from "@/api/container";
|
||||
import type { Container } from "@/api/container";
|
||||
import nodeApi from "@/api/node";
|
||||
import { useDataTable } from "@/utils/data-table";
|
||||
import { renderButton, renderLink, renderTag } from "@/utils/render";
|
||||
import { formatSize, renderButton, renderLink, renderTag } from "@/utils/render";
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
@ -82,18 +95,35 @@ const columns = [
|
||||
title: t('fields.actions'),
|
||||
key: "actions",
|
||||
render(i: Container, index: number) {
|
||||
return renderButton('error', t('buttons.delete'), () => deleteContainer(i, index), t('prompts.delete'))
|
||||
return renderButton('error', t('buttons.delete'), () => remove(i, index), t('prompts.delete'))
|
||||
},
|
||||
},
|
||||
];
|
||||
const { state, pagination, fetchData, changePageSize } = useDataTable(containerApi.search, filter, false)
|
||||
|
||||
async function deleteContainer(c: Container, index: number) {
|
||||
async function remove(c: Container, index: number) {
|
||||
const node = c.labels?.find(l => l.name === 'com.docker.swarm.node.id')
|
||||
await containerApi.delete(node?.value || '', c.id, '');
|
||||
await containerApi.delete(node?.value || '', c.id, c.name);
|
||||
state.data.splice(index, 1)
|
||||
}
|
||||
|
||||
async function prune() {
|
||||
window.dialog.warning({
|
||||
title: t('dialogs.prune_container.title'),
|
||||
content: t('dialogs.prune_container.body'),
|
||||
positiveText: t('buttons.confirm'),
|
||||
negativeText: t('buttons.cancel'),
|
||||
onPositiveClick: async () => {
|
||||
const r = await containerApi.prune(filter.node);
|
||||
window.message.info(t('texts.prune_container_success', {
|
||||
count: r.data?.count,
|
||||
size: formatSize(r.data?.size as number),
|
||||
}));
|
||||
fetchData();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const r = await nodeApi.list(true)
|
||||
nodes.value = r.data?.map(n => ({ label: n.name, value: n.id }))
|
||||
|
@ -1,5 +1,16 @@
|
||||
<template>
|
||||
<x-page-header />
|
||||
<x-page-header>
|
||||
<template #action>
|
||||
<n-button secondary size="small" type="warning" @click="prune">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<close-icon />
|
||||
</n-icon>
|
||||
</template>
|
||||
{{ t('buttons.prune') }}
|
||||
</n-button>
|
||||
</template>
|
||||
</x-page-header>
|
||||
<n-space class="page-body" vertical :size="12">
|
||||
<n-space :size="12">
|
||||
<n-select
|
||||
@ -38,7 +49,9 @@ import {
|
||||
NDataTable,
|
||||
NInput,
|
||||
NSelect,
|
||||
NIcon,
|
||||
} from "naive-ui";
|
||||
import { CloseOutline as CloseIcon } from "@vicons/ionicons5";
|
||||
import XPageHeader from "@/components/PageHeader.vue";
|
||||
import imageApi from "@/api/image";
|
||||
import type { Image } from "@/api/image";
|
||||
@ -86,17 +99,34 @@ const columns = [
|
||||
title: t('fields.actions'),
|
||||
key: "actions",
|
||||
render(i: Image, index: number) {
|
||||
return renderButton('error', t('buttons.delete'), () => deleteImage(i.id, index), t('prompts.delete'))
|
||||
return renderButton('error', t('buttons.delete'), () => remove(i.id, index), t('prompts.delete'))
|
||||
},
|
||||
},
|
||||
];
|
||||
const { state, pagination, fetchData, changePageSize } = useDataTable(imageApi.search, filter, false)
|
||||
|
||||
async function deleteImage(id: string, index: number) {
|
||||
async function remove(id: string, index: number) {
|
||||
await imageApi.delete(filter.node, id, "");
|
||||
state.data.splice(index, 1)
|
||||
}
|
||||
|
||||
async function prune() {
|
||||
window.dialog.warning({
|
||||
title: t('dialogs.prune_image.title'),
|
||||
content: t('dialogs.prune_image.body'),
|
||||
positiveText: t('buttons.confirm'),
|
||||
negativeText: t('buttons.cancel'),
|
||||
onPositiveClick: async () => {
|
||||
const r = await imageApi.prune(filter.node);
|
||||
window.message.info(t('texts.prune_image_success', {
|
||||
count: r.data?.count,
|
||||
size: formatSize(r.data?.size as number),
|
||||
}));
|
||||
fetchData();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const r = await nodeApi.list(true)
|
||||
nodes.value = r.data?.map(n => ({ label: n.name, value: n.id }))
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<x-page-header>
|
||||
<template #action>
|
||||
<n-button secondary size="small" type="warning" @click="pruneVolume">
|
||||
<n-button secondary size="small" type="warning" @click="prune">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<close-icon />
|
||||
@ -107,18 +107,18 @@ const columns = [
|
||||
title: t('fields.actions'),
|
||||
key: "actions",
|
||||
render(v: Volume, index: number) {
|
||||
return renderButton('error', t('buttons.delete'), () => deleteVolume(v.name, index), t('prompts.delete'))
|
||||
return renderButton('error', t('buttons.delete'), () => remove(v.name, index), t('prompts.delete'))
|
||||
},
|
||||
},
|
||||
];
|
||||
const { state, pagination, fetchData, changePageSize } = useDataTable(volumeApi.search, filter, false)
|
||||
|
||||
async function deleteVolume(name: string, index: number) {
|
||||
async function remove(name: string, index: number) {
|
||||
await volumeApi.delete(filter.node, name);
|
||||
state.data.splice(index, 1)
|
||||
}
|
||||
|
||||
async function pruneVolume() {
|
||||
async function prune() {
|
||||
window.dialog.warning({
|
||||
title: t('dialogs.prune_volume.title'),
|
||||
content: t('dialogs.prune_volume.body'),
|
||||
@ -127,8 +127,8 @@ async function pruneVolume() {
|
||||
onPositiveClick: async () => {
|
||||
const r = await volumeApi.prune(filter.node);
|
||||
window.message.info(t('texts.prune_volume_success', {
|
||||
count: r.data?.deletedVolumes.length,
|
||||
size: formatSize(r.data?.reclaimedSpace as number)
|
||||
count: r.data?.count,
|
||||
size: formatSize(r.data?.size as number)
|
||||
}));
|
||||
fetchData();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user