mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-09 23:17:21 +08:00
parent
9c5da23a38
commit
f50c473cf6
5 changed files with 139 additions and 55 deletions
|
@ -150,12 +150,13 @@ func (b *BaseApi) ImageRemove(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := imageService.ImageRemove(req); err != nil {
|
data, err := imageService.ImageRemove(req)
|
||||||
|
if err != nil {
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Container Image
|
// @Tags Container Image
|
||||||
|
|
|
@ -319,13 +319,30 @@ func (u *ContainerService) ContainerCreateByCommand(req dto.ContainerCreateByCom
|
||||||
if cmd.CheckIllegal(req.Command) {
|
if cmd.CheckIllegal(req.Command) {
|
||||||
return buserr.New(constant.ErrCmdIllegal)
|
return buserr.New(constant.ErrCmdIllegal)
|
||||||
}
|
}
|
||||||
taskItem, err := task.NewTaskWithOps("-", task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
if !strings.HasPrefix(strings.TrimSpace(req.Command), "docker run ") {
|
||||||
|
return errors.New("error command format")
|
||||||
|
}
|
||||||
|
containerName := ""
|
||||||
|
commands := strings.Split(req.Command, " ")
|
||||||
|
for index, val := range commands {
|
||||||
|
if val == "--name" && len(commands) > index+1 {
|
||||||
|
containerName = commands[index+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !strings.Contains(req.Command, " -d ") {
|
||||||
|
req.Command = strings.ReplaceAll(req.Command, "docker run", "docker run -d")
|
||||||
|
}
|
||||||
|
if len(containerName) == 0 {
|
||||||
|
containerName = fmt.Sprintf("1Panel-%s-%s", common.RandStr(5), common.RandStrAndNum(4))
|
||||||
|
req.Command += fmt.Sprintf(" --name %s", containerName)
|
||||||
|
}
|
||||||
|
taskItem, err := task.NewTaskWithOps(containerName, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", "-"), func(t *task.Task) error {
|
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", containerName), func(t *task.Task) error {
|
||||||
logPath := path.Join(constant.LogDir, task.TaskScopeContainer, req.TaskID+".log")
|
logPath := path.Join(constant.LogDir, task.TaskScopeContainer, req.TaskID+".log")
|
||||||
return cmd.ExecShell(logPath, 5*time.Minute, "bash", "-c", req.Command)
|
return cmd.ExecShell(logPath, 5*time.Minute, "bash", "-c", req.Command)
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
|
@ -40,7 +40,7 @@ type IImageService interface {
|
||||||
ImageLoad(req dto.ImageLoad) error
|
ImageLoad(req dto.ImageLoad) error
|
||||||
ImageSave(req dto.ImageSave) error
|
ImageSave(req dto.ImageSave) error
|
||||||
ImagePush(req dto.ImagePush) error
|
ImagePush(req dto.ImagePush) error
|
||||||
ImageRemove(req dto.BatchDelete) error
|
ImageRemove(req dto.BatchDelete) (dto.ContainerPruneReport, error)
|
||||||
ImageTag(req dto.ImageTag) error
|
ImageTag(req dto.ImageTag) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,24 +399,31 @@ func (u *ImageService) ImagePush(req dto.ImagePush) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
|
func (u *ImageService) ImageRemove(req dto.BatchDelete) (dto.ContainerPruneReport, error) {
|
||||||
|
report := dto.ContainerPruneReport{}
|
||||||
client, err := docker.NewDockerClient()
|
client, err := docker.NewDockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return report, err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
for _, id := range req.Names {
|
for _, id := range req.Names {
|
||||||
|
imageItem, _, err := client.ImageInspectWithRaw(context.TODO(), id)
|
||||||
|
if err != nil {
|
||||||
|
return report, err
|
||||||
|
}
|
||||||
if _, err := client.ImageRemove(context.TODO(), id, image.RemoveOptions{Force: req.Force, PruneChildren: true}); err != nil {
|
if _, err := client.ImageRemove(context.TODO(), id, image.RemoveOptions{Force: req.Force, PruneChildren: true}); err != nil {
|
||||||
if strings.Contains(err.Error(), "image is being used") || strings.Contains(err.Error(), "is using") {
|
if strings.Contains(err.Error(), "image is being used") || strings.Contains(err.Error(), "is using") {
|
||||||
if strings.Contains(id, "sha256:") {
|
if strings.Contains(id, "sha256:") {
|
||||||
return buserr.New(constant.ErrObjectInUsed)
|
return report, buserr.New(constant.ErrObjectInUsed)
|
||||||
}
|
}
|
||||||
return buserr.WithDetail(constant.ErrInUsed, id, nil)
|
return report, buserr.WithDetail(constant.ErrInUsed, id, nil)
|
||||||
}
|
}
|
||||||
return err
|
return report, err
|
||||||
}
|
}
|
||||||
|
report.DeletedNumber++
|
||||||
|
report.SpaceReclaimed += int(imageItem.Size)
|
||||||
}
|
}
|
||||||
return nil
|
return report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatFileSize(fileSize int64) (size string) {
|
func formatFileSize(fileSize int64) (size string) {
|
||||||
|
|
|
@ -94,7 +94,7 @@ export const imageTag = (params: Container.ImageTag) => {
|
||||||
return http.post(`/containers/image/tag`, params);
|
return http.post(`/containers/image/tag`, params);
|
||||||
};
|
};
|
||||||
export const imageRemove = (params: Container.BatchDelete) => {
|
export const imageRemove = (params: Container.BatchDelete) => {
|
||||||
return http.post(`/containers/image/remove`, params);
|
return http.post<Container.ContainerPruneReport>(`/containers/image/remove`, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
// network
|
// network
|
||||||
|
|
|
@ -1,67 +1,63 @@
|
||||||
<template>
|
<template>
|
||||||
<el-dialog v-model="dialogVisible" :destroy-on-close="true" :close-on-click-modal="false" width="30%">
|
<DrawerPro v-model="dialogVisible" :header="$t('container.imagePrune')" :back="handleClose" size="small">
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<span>{{ $t('container.imagePrune') }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<el-form ref="deleteForm" v-loading="loading">
|
<el-form ref="deleteForm" v-loading="loading">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-radio-group v-model="withTagAll">
|
<el-radio-group class="w-full" v-model="scope" @change="changeScope">
|
||||||
<el-radio :value="false">{{ $t('container.imagePruneSome') }}</el-radio>
|
<el-radio value="untag">{{ $t('container.imagePruneSome') }}</el-radio>
|
||||||
<el-radio :value="true">{{ $t('container.imagePruneAll') }}</el-radio>
|
<el-radio value="unused">{{ $t('container.imagePruneAll') }}</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
|
<span class="input-help">{{ showMsg }}</span>
|
||||||
|
<el-checkbox
|
||||||
|
class="w-full"
|
||||||
|
v-if="data.length !== 0"
|
||||||
|
v-model="checkAll"
|
||||||
|
:indeterminate="isIndeterminate"
|
||||||
|
@change="handleCheckAllChange"
|
||||||
|
>
|
||||||
|
{{ $t('commons.table.all') }}
|
||||||
|
</el-checkbox>
|
||||||
|
<el-checkbox-group v-model="checkedLists" @change="handleCheckedChange">
|
||||||
|
<el-checkbox class="w-full" v-for="(item, index) in data" :key="index" :value="item.id">
|
||||||
|
{{
|
||||||
|
item.tags && item.tags[0]
|
||||||
|
? item.tags[0]
|
||||||
|
: item.id.replaceAll('sha256:', '').substring(0, 12)
|
||||||
|
}}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<span v-if="withTagAll">
|
|
||||||
{{ unUsedList.length !== 0 ? $t('container.imagePruneAllHelper') : $t('container.imagePruneAllEmpty') }}
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
{{
|
|
||||||
unTagList.length !== 0 ? $t('container.imagePruneSomeHelper') : $t('container.imagePruneSomeEmpty')
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
<div v-if="!withTagAll">
|
|
||||||
<ul v-for="(item, index) in unTagList" :key="index">
|
|
||||||
<li v-if="item.tags && item.tags[0]">
|
|
||||||
{{ item.tags[0] }}
|
|
||||||
</li>
|
|
||||||
<li v-else>
|
|
||||||
{{ item.id.replaceAll('sha256:', '').substring(0, 12) }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<ul v-for="(item, index) in unUsedList" :key="index">
|
|
||||||
<li v-if="item.tags && item.tags[0]">{{ item.tags.join(', ') }}</li>
|
|
||||||
<li v-else>{{ item.id.replaceAll('sha256:', '').substring(0, 12) }}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="dialogVisible = false">
|
<el-button @click="dialogVisible = false">
|
||||||
{{ $t('commons.button.cancel') }}
|
{{ $t('commons.button.cancel') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" :disabled="buttonDisable() || loading" @click="onClean">
|
<el-button type="primary" :disabled="data.length === 0 || loading" @click="onClean">
|
||||||
{{ $t('commons.button.confirm') }}
|
{{ $t('commons.button.confirm') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</DrawerPro>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { containerPrune, listAllImage } from '@/api/modules/container';
|
import { containerPrune, imageRemove, listAllImage } from '@/api/modules/container';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
import { computeSize } from '@/utils/util';
|
import { computeSize } from '@/utils/util';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
const withTagAll = ref(false);
|
const scope = ref('untag');
|
||||||
|
const showMsg = ref();
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const unTagList = ref();
|
const unTagList = ref([]);
|
||||||
const unUsedList = ref();
|
const unUsedList = ref([]);
|
||||||
|
const data = ref([]);
|
||||||
|
|
||||||
|
const checkAll = ref(false);
|
||||||
|
const isIndeterminate = ref(false);
|
||||||
|
const checkedLists = ref([]);
|
||||||
|
|
||||||
const acceptParams = async (): Promise<void> => {
|
const acceptParams = async (): Promise<void> => {
|
||||||
const res = await listAllImage();
|
const res = await listAllImage();
|
||||||
|
@ -81,20 +77,62 @@ const acceptParams = async (): Promise<void> => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
withTagAll.value = false;
|
scope.value = 'untag';
|
||||||
|
changeScope();
|
||||||
};
|
};
|
||||||
|
|
||||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
const buttonDisable = () => {
|
const changeScope = () => {
|
||||||
return withTagAll.value ? unUsedList.value.length === 0 : unTagList.value.length === 0;
|
if (scope.value === 'untag') {
|
||||||
|
data.value = unTagList.value || [];
|
||||||
|
showMsg.value =
|
||||||
|
data.value.length === 0
|
||||||
|
? i18n.global.t('container.imagePruneSomeHelper')
|
||||||
|
: i18n.global.t('container.imagePruneSomeEmpty');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.value = unUsedList.value || [];
|
||||||
|
showMsg.value =
|
||||||
|
data.value.length === 0
|
||||||
|
? i18n.global.t('container.imagePruneAllHelper')
|
||||||
|
: i18n.global.t('container.imagePruneAllEmpty');
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckAllChange = (val: boolean) => {
|
||||||
|
checkedLists.value = [];
|
||||||
|
if (!val) {
|
||||||
|
isIndeterminate.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const item of data.value) {
|
||||||
|
checkedLists.value.push(item.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleCheckedChange = (value: string[]) => {
|
||||||
|
const checkedCount = value.length;
|
||||||
|
checkAll.value = checkedCount === unUsedList.value.length;
|
||||||
|
isIndeterminate.value = checkedCount > 0 && checkedCount < unUsedList.value.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
dialogVisible.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClean = async () => {
|
const onClean = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
if (checkAll.value) {
|
||||||
|
prune();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeImage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const prune = async () => {
|
||||||
let params = {
|
let params = {
|
||||||
pruneType: 'image',
|
pruneType: 'image',
|
||||||
withTagAll: withTagAll.value,
|
withTagAll: scope.value === 'unused',
|
||||||
};
|
};
|
||||||
await containerPrune(params)
|
await containerPrune(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
@ -113,6 +151,27 @@ const onClean = async () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeImage = async () => {
|
||||||
|
let params = {
|
||||||
|
names: checkedLists.value,
|
||||||
|
};
|
||||||
|
await imageRemove(params)
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
dialogVisible.value = false;
|
||||||
|
MsgSuccess(
|
||||||
|
i18n.global.t('container.cleanSuccessWithSpace', [
|
||||||
|
res.data.deletedNumber,
|
||||||
|
computeSize(res.data.spaceReclaimed),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
emit('search');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
acceptParams,
|
acceptParams,
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue