mirror of
https://github.com/cuigh/swirl
synced 2025-06-26 18:16:50 +00:00
Add prune function to image and container
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user