1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-14 01:34:47 +08:00

feat: 容器镜像列表增加是否使用标签 (#2364)

Refs #2300 

![image](https://github.com/1Panel-dev/1Panel/assets/73214554/a808d4f8-18fa-4010-8860-0a3788fa7eb0)
This commit is contained in:
ssongliu 2023-09-20 22:14:25 +08:00 committed by GitHub
parent 695f3278c3
commit 12d6c2c3df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 57 additions and 11 deletions

View File

@ -5,6 +5,7 @@ import "time"
type ImageInfo struct { type ImageInfo struct {
ID string `json:"id"` ID string `json:"id"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
IsUsed bool `json:"isUsed"`
Tags []string `json:"tags"` Tags []string `json:"tags"`
Size string `json:"size"` Size string `json:"size"`
} }

View File

@ -53,6 +53,7 @@ func (u *ImageService) Page(req dto.SearchWithPage) (int64, interface{}, error)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
containers, _ := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
if len(req.Info) != 0 { if len(req.Info) != 0 {
length, count := len(list), 0 length, count := len(list), 0
for count < length { for count < length {
@ -77,6 +78,7 @@ func (u *ImageService) Page(req dto.SearchWithPage) (int64, interface{}, error)
records = append(records, dto.ImageInfo{ records = append(records, dto.ImageInfo{
ID: image.ID, ID: image.ID,
Tags: image.RepoTags, Tags: image.RepoTags,
IsUsed: checkUsed(image.ID, containers),
CreatedAt: time.Unix(image.Created, 0), CreatedAt: time.Unix(image.Created, 0),
Size: size, Size: size,
}) })
@ -415,3 +417,12 @@ func formatFileSize(fileSize int64) (size string) {
return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024)) return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024))
} }
} }
func checkUsed(imageID string, containers []types.Container) bool {
for _, container := range containers {
if container.ImageID == imageID {
return true
}
}
return false
}

View File

@ -224,6 +224,8 @@ const message = {
rebuilding: 'ReBuilding', rebuilding: 'ReBuilding',
deny: 'Denied', deny: 'Denied',
accept: 'Accepted', accept: 'Accepted',
used: 'Used',
unUsed: 'Unused',
}, },
units: { units: {
second: 'Second', second: 'Second',
@ -493,9 +495,9 @@ const message = {
containerPruneHelper3: 'This operation cannot be rolled back. Do you want to continue?', containerPruneHelper3: 'This operation cannot be rolled back. Do you want to continue?',
imagePrune: 'Image prune', imagePrune: 'Image prune',
imagePruneSome: 'Clean unlabeled', imagePruneSome: 'Clean unlabeled',
imagePruneSomeHelper: 'Remove all unused and unlabeled container images', imagePruneSomeHelper: 'Clean the images with the tag "none" that are not used by any containers.',
imagePruneAll: 'Clean unused', imagePruneAll: 'Clean unused',
imagePruneAllHelper: 'Remove all unused images, not just unlabeled', imagePruneAllHelper: 'Clean the images that are not used by any containers.',
networkPrune: 'Network prune', networkPrune: 'Network prune',
networkPruneHelper: 'Remove all unused networks. Do you want to continue?', networkPruneHelper: 'Remove all unused networks. Do you want to continue?',
volumePrune: 'Volume prune', volumePrune: 'Volume prune',

View File

@ -222,6 +222,8 @@ const message = {
rebuilding: '重建中', rebuilding: '重建中',
deny: '已屏蔽', deny: '已屏蔽',
accept: '已放行', accept: '已放行',
used: '已使用',
unUsed: '未使用',
}, },
units: { units: {
second: '秒', second: '秒',
@ -481,9 +483,9 @@ const message = {
containerPruneHelper3: '該操作無法回滾是否繼續', containerPruneHelper3: '該操作無法回滾是否繼續',
imagePrune: '清理鏡像', imagePrune: '清理鏡像',
imagePruneSome: '未標簽鏡像', imagePruneSome: '未標簽鏡像',
imagePruneSomeHelper: '清理標簽為 none 且未被任何容器使用的鏡像', imagePruneSomeHelper: '清理下列標簽為 none 且未被任何容器使用的鏡像',
imagePruneAll: '未使用鏡像', imagePruneAll: '未使用鏡像',
imagePruneAllHelper: '清理所有未被任何容器使用的鏡像', imagePruneAllHelper: '清理下列未被任何容器使用的鏡像',
networkPrune: '清理網絡', networkPrune: '清理網絡',
networkPruneHelper: '清理網絡 將刪除所有未被使用的網絡該操作無法回滾是否繼續', networkPruneHelper: '清理網絡 將刪除所有未被使用的網絡該操作無法回滾是否繼續',
volumePrune: '清理存儲卷', volumePrune: '清理存儲卷',

View File

@ -222,6 +222,8 @@ const message = {
rebuilding: '重建中', rebuilding: '重建中',
deny: '已屏蔽', deny: '已屏蔽',
accept: '已放行', accept: '已放行',
used: '已使用',
unUsed: '未使用',
}, },
units: { units: {
second: '秒', second: '秒',
@ -481,9 +483,9 @@ const message = {
containerPruneHelper3: '该操作无法回滚是否继续', containerPruneHelper3: '该操作无法回滚是否继续',
imagePrune: '清理镜像', imagePrune: '清理镜像',
imagePruneSome: '未标签镜像', imagePruneSome: '未标签镜像',
imagePruneSomeHelper: '清理标签为 none 且未被任何容器使用的镜像', imagePruneSomeHelper: '清理下列标签为 none 且未被任何容器使用的镜像',
imagePruneAll: '未使用镜像', imagePruneAll: '未使用镜像',
imagePruneAllHelper: '清理所有未被任何容器使用的镜像', imagePruneAllHelper: '清理下列未被任何容器使用的镜像',
networkPrune: '清理网络', networkPrune: '清理网络',
networkPruneHelper: '清理网络 将删除所有未被使用的网络该操作无法回滚是否继续', networkPruneHelper: '清理网络 将删除所有未被使用的网络该操作无法回滚是否继续',
volumePrune: '清理存储卷', volumePrune: '清理存储卷',

View File

@ -46,6 +46,16 @@
<span>{{ row.id.replaceAll('sha256:', '').substring(0, 12) }}</span> <span>{{ row.id.replaceAll('sha256:', '').substring(0, 12) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('commons.table.status')" prop="isUsed" width="100">
<template #default="{ row }">
<el-tag icon="Select" v-if="row.isUsed" type="success">
{{ $t('commons.status.used') }}
</el-tag>
<el-tag v-else type="info">
{{ $t('commons.status.unUsed') }}
</el-tag>
</template>
</el-table-column>
<el-table-column <el-table-column
:label="$t('container.tag')" :label="$t('container.tag')"
prop="tags" prop="tags"
@ -186,7 +196,7 @@ const onOpenBuild = () => {
}; };
const onOpenPrune = () => { const onOpenPrune = () => {
dialogPruneRef.value!.acceptParams(); dialogPruneRef.value!.acceptParams({ list: data.value });
}; };
const onOpenload = () => { const onOpenload = () => {

View File

@ -11,10 +11,22 @@
<el-radio :label="false">{{ $t('container.imagePruneSome') }}</el-radio> <el-radio :label="false">{{ $t('container.imagePruneSome') }}</el-radio>
<el-radio :label="true">{{ $t('container.imagePruneAll') }}</el-radio> <el-radio :label="true">{{ $t('container.imagePruneAll') }}</el-radio>
</el-radio-group> </el-radio-group>
<span class="input-help">
{{ withTagAll ? $t('container.imagePruneAllHelper') : $t('container.imagePruneSomeHelper') }}
</span>
</el-form-item> </el-form-item>
<span>
{{ withTagAll ? $t('container.imagePruneAllHelper') : $t('container.imagePruneSomeHelper') }}
</span>
<div v-if="!withTagAll">
<ul v-for="(item, index) in imageList" :key="index">
<li v-if="item.tags.length === 1 && item.tags[0].indexOf(':<none>') !== -1">
{{ item.tags[0] }}
</li>
</ul>
</div>
<div v-else>
<ul v-for="(item, index) in imageList" :key="index">
<li v-if="!item.isUsed">{{ item.tags.join(', ') }}</li>
</ul>
</div>
</el-form> </el-form>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
@ -30,6 +42,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Container } from '@/api/interface/container';
import { containerPrune } from '@/api/modules/container'; import { containerPrune } from '@/api/modules/container';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
@ -39,8 +52,13 @@ import { ref } from 'vue';
const dialogVisiable = ref(false); const dialogVisiable = ref(false);
const withTagAll = ref(false); const withTagAll = ref(false);
const loading = ref(); const loading = ref();
const imageList = ref();
const acceptParams = (): void => { interface DialogProps {
list: Array<Container.ImageInfo>;
}
const acceptParams = (params: DialogProps): void => {
imageList.value = params.list;
dialogVisiable.value = true; dialogVisiable.value = true;
withTagAll.value = false; withTagAll.value = false;
}; };