feat: 镜像 Tag 逻辑修改 (#3346)

Refs #3316
This commit is contained in:
ssongliu 2023-12-15 16:22:08 +08:00 committed by GitHub
parent 3fb5e523a8
commit 4d279a521e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 77 additions and 46 deletions

View file

@ -27,7 +27,6 @@ type ImagePull struct {
} }
type ImageTag struct { type ImageTag struct {
RepoID uint `json:"repoID"`
SourceID string `json:"sourceID" validate:"required"` SourceID string `json:"sourceID" validate:"required"`
TargetName string `json:"targetName" validate:"required"` TargetName string `json:"targetName" validate:"required"`
} }

View file

@ -15460,9 +15460,6 @@ const docTemplate = `{
"targetName" "targetName"
], ],
"properties": { "properties": {
"repoID": {
"type": "integer"
},
"sourceID": { "sourceID": {
"type": "string" "type": "string"
}, },
@ -18301,6 +18298,9 @@ const docTemplate = `{
"url" "url"
], ],
"properties": { "properties": {
"ignoreCertificate": {
"type": "boolean"
},
"name": { "name": {
"type": "string" "type": "string"
}, },

View file

@ -15453,9 +15453,6 @@
"targetName" "targetName"
], ],
"properties": { "properties": {
"repoID": {
"type": "integer"
},
"sourceID": { "sourceID": {
"type": "string" "type": "string"
}, },
@ -18294,6 +18291,9 @@
"url" "url"
], ],
"properties": { "properties": {
"ignoreCertificate": {
"type": "boolean"
},
"name": { "name": {
"type": "string" "type": "string"
}, },

View file

@ -1398,8 +1398,6 @@ definitions:
type: object type: object
dto.ImageTag: dto.ImageTag:
properties: properties:
repoID:
type: integer
sourceID: sourceID:
type: string type: string
targetName: targetName:
@ -3300,6 +3298,8 @@ definitions:
type: object type: object
request.FileWget: request.FileWget:
properties: properties:
ignoreCertificate:
type: boolean
name: name:
type: string type: string
path: path:

View file

@ -133,7 +133,6 @@ export namespace Container {
imageName: string; imageName: string;
} }
export interface ImageTag { export interface ImageTag {
repoID: number;
sourceID: string; sourceID: string;
targetName: string; targetName: string;
} }

View file

@ -635,6 +635,7 @@ const message = {
imagePush: 'Image push', imagePush: 'Image push',
imageDelete: 'Image delete', imageDelete: 'Image delete',
imageDeleteTag: 'Image tag delete', imageDeleteTag: 'Image tag delete',
imageTagDeleteHelper: 'Remove other tags associated with this image ID',
repoName: 'Repo Name', repoName: 'Repo Name',
imageName: 'Image name', imageName: 'Image name',
pull: 'Pull', pull: 'Pull',

View file

@ -617,6 +617,7 @@ const message = {
imagePush: '推送鏡像', imagePush: '推送鏡像',
imageDelete: '刪除鏡像', imageDelete: '刪除鏡像',
imageDeleteTag: '刪除 Tag', imageDeleteTag: '刪除 Tag',
imageTagDeleteHelper: '移除與該映像 ID 相關聯的其他標籤',
repoName: '倉庫名', repoName: '倉庫名',
imageName: '鏡像名', imageName: '鏡像名',
httpRepo: 'http 倉庫添加授信需要重啟 docker 服務', httpRepo: 'http 倉庫添加授信需要重啟 docker 服務',

View file

@ -618,6 +618,7 @@ const message = {
imagePush: '推送镜像', imagePush: '推送镜像',
imageDelete: '删除镜像', imageDelete: '删除镜像',
imageDeleteTag: '删除 Tag', imageDeleteTag: '删除 Tag',
imageTagDeleteHelper: '移除与该镜像 ID 相关联的其他标签',
repoName: '仓库名', repoName: '仓库名',
imageName: '镜像名', imageName: '镜像名',
httpRepo: 'http 仓库添加授信需要重启 docker 服务', httpRepo: 'http 仓库添加授信需要重启 docker 服务',

View file

@ -94,6 +94,8 @@ const acceptParams = (props: DialogProps): void => {
form.hasName = props.image.indexOf('sha256:') === -1; form.hasName = props.image.indexOf('sha256:') === -1;
if (form.hasName) { if (form.hasName) {
form.newImageName = props.image; form.newImageName = props.image;
} else {
form.newImageName = '';
} }
drawerVisible.value = true; drawerVisible.value = true;
}; };

View file

@ -17,7 +17,7 @@
{{ $t('container.removeAll') }} {{ $t('container.removeAll') }}
</el-checkbox> </el-checkbox>
</div> </div>
<el-checkbox-group v-model="form.deleteTags" @change="handleCheckedCitiesChange"> <el-checkbox-group v-model="form.deleteTags" @change="handleCheckedChange">
<div> <div>
<el-checkbox <el-checkbox
style="width: 100%" style="width: 100%"
@ -91,7 +91,7 @@ const handleCheckAllChange = (val: boolean) => {
form.deleteTags = val ? form.tags : []; form.deleteTags = val ? form.tags : [];
isIndeterminate.value = false; isIndeterminate.value = false;
}; };
const handleCheckedCitiesChange = (value: string[]) => { const handleCheckedChange = (value: string[]) => {
const checkedCount = value.length; const checkedCount = value.length;
deleteAll.value = checkedCount === form.tags.length; deleteAll.value = checkedCount === form.tags.length;
isIndeterminate.value = checkedCount > 0 && checkedCount < form.tags.length; isIndeterminate.value = checkedCount > 0 && checkedCount < form.tags.length;

View file

@ -243,9 +243,9 @@ const buttons = [
label: i18n.global.t('container.tag'), label: i18n.global.t('container.tag'),
click: (row: Container.ImageInfo) => { click: (row: Container.ImageInfo) => {
let params = { let params = {
itemName: row.tags && row.tags?.length !== 0 ? row.tags[0].split(':')[0] : '',
repos: repos.value, repos: repos.value,
sourceID: row.id, imageID: row.id,
tags: row.tags,
}; };
dialogTagRef.value!.acceptParams(params); dialogTagRef.value!.acceptParams(params);
}, },

View file

@ -1,7 +1,7 @@
<template> <template>
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%"> <el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
<template #header> <template #header>
<DrawerHeader :header="$t('container.imageTag')" :resource="form.itemName" :back="handleClose" /> <DrawerHeader :header="$t('container.imageTag')" :back="handleClose" />
</template> </template>
<el-form v-loading="loading" label-position="top" ref="formRef" :model="form" label-width="80px"> <el-form v-loading="loading" label-position="top" ref="formRef" :model="form" label-width="80px">
<el-row type="flex" justify="center"> <el-row type="flex" justify="center">
@ -13,16 +13,29 @@
v-if="form.fromRepo" v-if="form.fromRepo"
:label="$t('container.repoName')" :label="$t('container.repoName')"
:rules="Rules.requiredSelect" :rules="Rules.requiredSelect"
prop="repoID" prop="repo"
> >
<el-select style="width: 100%" filterable v-model="form.repoID"> <el-select style="width: 100%" filterable v-model="form.repo" @change="changeRepo">
<el-option v-for="item in repos" :key="item.id" :value="item.id" :label="item.name" /> <el-option v-for="item in repos" :key="item.id" :value="item.name" :label="item.name" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="$t('container.imageName')" :rules="Rules.imageName" prop="targetName"> <el-form-item :label="$t('container.imageTag')" :rules="Rules.imageName" prop="targetName">
<el-input v-model="form.targetName"> <el-input v-model="form.targetName" />
<template v-if="form.fromRepo" #prepend>{{ loadDetailInfo(form.repoID) }}/</template> </el-form-item>
</el-input>
<el-form-item>
<el-checkbox style="width: 100%" v-model="form.deleteTag">
{{ $t('container.imageTagDeleteHelper') }}
</el-checkbox>
<el-checkbox-group class="ml-5" v-if="form.deleteTag" v-model="form.deleteTags">
<el-checkbox
style="width: 100%"
v-for="item in tags"
:key="item"
:value="item"
:label="item"
/>
</el-checkbox-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -46,7 +59,7 @@ import { reactive, ref } from 'vue';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import i18n from '@/lang'; import i18n from '@/lang';
import { ElForm } from 'element-plus'; import { ElForm } from 'element-plus';
import { imageTag } from '@/api/modules/container'; import { imageRemove, imageTag } from '@/api/modules/container';
import { Container } from '@/api/interface/container'; import { Container } from '@/api/interface/container';
import DrawerHeader from '@/components/drawer-header/index.vue'; import DrawerHeader from '@/components/drawer-header/index.vue';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
@ -55,28 +68,35 @@ const loading = ref(false);
const drawerVisible = ref(false); const drawerVisible = ref(false);
const repos = ref(); const repos = ref();
const tags = ref();
const form = reactive({ const form = reactive({
itemName: '', imageID: '',
sourceID: '', fromRepo: false,
fromRepo: true, repo: '',
repoID: 1, originName: '',
targetName: '', targetName: '',
deleteTag: false,
deleteTags: [],
}); });
interface DialogProps { interface DialogProps {
itemName: string;
repos: Array<Container.RepoOptions>; repos: Array<Container.RepoOptions>;
sourceID: string; imageID: string;
tags: Array<string>;
} }
const acceptParams = async (params: DialogProps): Promise<void> => { const acceptParams = async (params: DialogProps): Promise<void> => {
drawerVisible.value = true; drawerVisible.value = true;
form.repoID = 1; form.imageID = params.imageID;
form.itemName = params.itemName; form.originName = params.tags?.length !== 0 ? params.tags[0] : '';
form.sourceID = params.sourceID; form.targetName = params.tags?.length !== 0 ? params.tags[0] : '';
form.targetName = ''; form.fromRepo = false;
form.fromRepo = true; form.repo = '';
form.deleteTag = false;
form.deleteTags = [];
repos.value = params.repos; repos.value = params.repos;
tags.value = params.tags;
}; };
const emit = defineEmits<{ (e: 'search'): void }>(); const emit = defineEmits<{ (e: 'search'): void }>();
@ -91,13 +111,17 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate(async (valid) => { formEl.validate(async (valid) => {
if (!valid) return; if (!valid) return;
if (!form.fromRepo) { let params = {
form.repoID = 0; sourceID: form.imageID,
} targetName: form.targetName,
};
loading.value = true; loading.value = true;
await imageTag(form) await imageTag(params)
.then(() => { .then(async () => {
loading.value = false; loading.value = false;
if (form.deleteTag && form.deleteTags.length !== 0) {
await imageRemove({ names: form.deleteTags });
}
drawerVisible.value = false; drawerVisible.value = false;
emit('search'); emit('search');
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
@ -108,14 +132,18 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
}); });
}; };
function loadDetailInfo(id: number) { const changeRepo = (val) => {
if (val === 'Docker Hub') {
form.targetName = form.originName;
return;
}
for (const item of repos.value) { for (const item of repos.value) {
if (item.id === id) { if (item.name == val) {
return item.downloadUrl; form.targetName = item.downloadUrl + '/' + form.originName;
return;
} }
} }
return ''; };
}
defineExpose({ defineExpose({
acceptParams, acceptParams,

View file

@ -147,7 +147,7 @@ const loadData = (path: string) => {
item.size = itemSize.size; item.size = itemSize.size;
item.sizeUnit = itemSize.unit; item.sizeUnit = itemSize.unit;
} }
if (!isExist && path !== '') { if (!isExist) {
form.swapDetails.push({ form.swapDetails.push({
path: path + '/.1panel_swap', path: path + '/.1panel_swap',
size: 0, size: 0,