From 4d279a521e6e434584da18ad1d122869bcce0490 Mon Sep 17 00:00:00 2001
From: ssongliu <73214554+ssongliu@users.noreply.github.com>
Date: Fri, 15 Dec 2023 16:22:08 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E9=95=9C=E5=83=8F=20Tag=20=E9=80=BB?=
 =?UTF-8?q?=E8=BE=91=E4=BF=AE=E6=94=B9=20(#3346)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Refs #3316
---
 backend/app/dto/image.go                      |  1 -
 cmd/server/docs/docs.go                       |  6 +-
 cmd/server/docs/swagger.json                  |  6 +-
 cmd/server/docs/swagger.yaml                  |  4 +-
 frontend/src/api/interface/container.ts       |  1 -
 frontend/src/lang/modules/en.ts               |  1 +
 frontend/src/lang/modules/tw.ts               |  1 +
 frontend/src/lang/modules/zh.ts               |  1 +
 .../container/container/upgrade/index.vue     |  2 +
 .../views/container/image/delete/index.vue    |  4 +-
 frontend/src/views/container/image/index.vue  |  4 +-
 .../src/views/container/image/tag/index.vue   | 90 ++++++++++++-------
 .../src/views/toolbox/device/swap/index.vue   |  2 +-
 13 files changed, 77 insertions(+), 46 deletions(-)

diff --git a/backend/app/dto/image.go b/backend/app/dto/image.go
index 892e0a24f..541c5ffb0 100644
--- a/backend/app/dto/image.go
+++ b/backend/app/dto/image.go
@@ -27,7 +27,6 @@ type ImagePull struct {
 }
 
 type ImageTag struct {
-	RepoID     uint   `json:"repoID"`
 	SourceID   string `json:"sourceID" validate:"required"`
 	TargetName string `json:"targetName" validate:"required"`
 }
diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go
index f46955f50..b0aa252fc 100644
--- a/cmd/server/docs/docs.go
+++ b/cmd/server/docs/docs.go
@@ -15460,9 +15460,6 @@ const docTemplate = `{
                 "targetName"
             ],
             "properties": {
-                "repoID": {
-                    "type": "integer"
-                },
                 "sourceID": {
                     "type": "string"
                 },
@@ -18301,6 +18298,9 @@ const docTemplate = `{
                 "url"
             ],
             "properties": {
+                "ignoreCertificate": {
+                    "type": "boolean"
+                },
                 "name": {
                     "type": "string"
                 },
diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json
index 2077d314f..0a120cffe 100644
--- a/cmd/server/docs/swagger.json
+++ b/cmd/server/docs/swagger.json
@@ -15453,9 +15453,6 @@
                 "targetName"
             ],
             "properties": {
-                "repoID": {
-                    "type": "integer"
-                },
                 "sourceID": {
                     "type": "string"
                 },
@@ -18294,6 +18291,9 @@
                 "url"
             ],
             "properties": {
+                "ignoreCertificate": {
+                    "type": "boolean"
+                },
                 "name": {
                     "type": "string"
                 },
diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml
index 0b1a6146b..053b975ec 100644
--- a/cmd/server/docs/swagger.yaml
+++ b/cmd/server/docs/swagger.yaml
@@ -1398,8 +1398,6 @@ definitions:
     type: object
   dto.ImageTag:
     properties:
-      repoID:
-        type: integer
       sourceID:
         type: string
       targetName:
@@ -3300,6 +3298,8 @@ definitions:
     type: object
   request.FileWget:
     properties:
+      ignoreCertificate:
+        type: boolean
       name:
         type: string
       path:
diff --git a/frontend/src/api/interface/container.ts b/frontend/src/api/interface/container.ts
index e348c3ddd..baef4e432 100644
--- a/frontend/src/api/interface/container.ts
+++ b/frontend/src/api/interface/container.ts
@@ -133,7 +133,6 @@ export namespace Container {
         imageName: string;
     }
     export interface ImageTag {
-        repoID: number;
         sourceID: string;
         targetName: string;
     }
diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts
index 705ec230b..842d97593 100644
--- a/frontend/src/lang/modules/en.ts
+++ b/frontend/src/lang/modules/en.ts
@@ -635,6 +635,7 @@ const message = {
         imagePush: 'Image push',
         imageDelete: 'Image delete',
         imageDeleteTag: 'Image tag delete',
+        imageTagDeleteHelper: 'Remove other tags associated with this image ID',
         repoName: 'Repo Name',
         imageName: 'Image name',
         pull: 'Pull',
diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts
index 71d871de6..dd87f3162 100644
--- a/frontend/src/lang/modules/tw.ts
+++ b/frontend/src/lang/modules/tw.ts
@@ -617,6 +617,7 @@ const message = {
         imagePush: '推送鏡像',
         imageDelete: '刪除鏡像',
         imageDeleteTag: '刪除 Tag',
+        imageTagDeleteHelper: '移除與該映像 ID 相關聯的其他標籤',
         repoName: '倉庫名',
         imageName: '鏡像名',
         httpRepo: 'http 倉庫添加授信需要重啟 docker 服務',
diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts
index 09090b1de..30d296f48 100644
--- a/frontend/src/lang/modules/zh.ts
+++ b/frontend/src/lang/modules/zh.ts
@@ -618,6 +618,7 @@ const message = {
         imagePush: '推送镜像',
         imageDelete: '删除镜像',
         imageDeleteTag: '删除 Tag',
+        imageTagDeleteHelper: '移除与该镜像 ID 相关联的其他标签',
         repoName: '仓库名',
         imageName: '镜像名',
         httpRepo: 'http 仓库添加授信需要重启 docker 服务',
diff --git a/frontend/src/views/container/container/upgrade/index.vue b/frontend/src/views/container/container/upgrade/index.vue
index e44d52ed9..aa5a01230 100644
--- a/frontend/src/views/container/container/upgrade/index.vue
+++ b/frontend/src/views/container/container/upgrade/index.vue
@@ -94,6 +94,8 @@ const acceptParams = (props: DialogProps): void => {
     form.hasName = props.image.indexOf('sha256:') === -1;
     if (form.hasName) {
         form.newImageName = props.image;
+    } else {
+        form.newImageName = '';
     }
     drawerVisible.value = true;
 };
diff --git a/frontend/src/views/container/image/delete/index.vue b/frontend/src/views/container/image/delete/index.vue
index 870d73f6a..f545e5962 100644
--- a/frontend/src/views/container/image/delete/index.vue
+++ b/frontend/src/views/container/image/delete/index.vue
@@ -17,7 +17,7 @@
                                     {{ $t('container.removeAll') }}
                                 </el-checkbox>
                             </div>
-                            <el-checkbox-group v-model="form.deleteTags" @change="handleCheckedCitiesChange">
+                            <el-checkbox-group v-model="form.deleteTags" @change="handleCheckedChange">
                                 <div>
                                     <el-checkbox
                                         style="width: 100%"
@@ -91,7 +91,7 @@ const handleCheckAllChange = (val: boolean) => {
     form.deleteTags = val ? form.tags : [];
     isIndeterminate.value = false;
 };
-const handleCheckedCitiesChange = (value: string[]) => {
+const handleCheckedChange = (value: string[]) => {
     const checkedCount = value.length;
     deleteAll.value = checkedCount === form.tags.length;
     isIndeterminate.value = checkedCount > 0 && checkedCount < form.tags.length;
diff --git a/frontend/src/views/container/image/index.vue b/frontend/src/views/container/image/index.vue
index a0f50ff8a..39ba966bb 100644
--- a/frontend/src/views/container/image/index.vue
+++ b/frontend/src/views/container/image/index.vue
@@ -243,9 +243,9 @@ const buttons = [
         label: i18n.global.t('container.tag'),
         click: (row: Container.ImageInfo) => {
             let params = {
-                itemName: row.tags && row.tags?.length !== 0 ? row.tags[0].split(':')[0] : '',
                 repos: repos.value,
-                sourceID: row.id,
+                imageID: row.id,
+                tags: row.tags,
             };
             dialogTagRef.value!.acceptParams(params);
         },
diff --git a/frontend/src/views/container/image/tag/index.vue b/frontend/src/views/container/image/tag/index.vue
index 3f5ea035e..f89b5b114 100644
--- a/frontend/src/views/container/image/tag/index.vue
+++ b/frontend/src/views/container/image/tag/index.vue
@@ -1,7 +1,7 @@
 <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>
-            <DrawerHeader :header="$t('container.imageTag')" :resource="form.itemName" :back="handleClose" />
+            <DrawerHeader :header="$t('container.imageTag')" :back="handleClose" />
         </template>
         <el-form v-loading="loading" label-position="top" ref="formRef" :model="form" label-width="80px">
             <el-row type="flex" justify="center">
@@ -13,16 +13,29 @@
                         v-if="form.fromRepo"
                         :label="$t('container.repoName')"
                         :rules="Rules.requiredSelect"
-                        prop="repoID"
+                        prop="repo"
                     >
-                        <el-select style="width: 100%" filterable v-model="form.repoID">
-                            <el-option v-for="item in repos" :key="item.id" :value="item.id" :label="item.name" />
+                        <el-select style="width: 100%" filterable v-model="form.repo" @change="changeRepo">
+                            <el-option v-for="item in repos" :key="item.id" :value="item.name" :label="item.name" />
                         </el-select>
                     </el-form-item>
-                    <el-form-item :label="$t('container.imageName')" :rules="Rules.imageName" prop="targetName">
-                        <el-input v-model="form.targetName">
-                            <template v-if="form.fromRepo" #prepend>{{ loadDetailInfo(form.repoID) }}/</template>
-                        </el-input>
+                    <el-form-item :label="$t('container.imageTag')" :rules="Rules.imageName" prop="targetName">
+                        <el-input v-model="form.targetName" />
+                    </el-form-item>
+
+                    <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-col>
             </el-row>
@@ -46,7 +59,7 @@ import { reactive, ref } from 'vue';
 import { Rules } from '@/global/form-rules';
 import i18n from '@/lang';
 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 DrawerHeader from '@/components/drawer-header/index.vue';
 import { MsgSuccess } from '@/utils/message';
@@ -55,28 +68,35 @@ const loading = ref(false);
 
 const drawerVisible = ref(false);
 const repos = ref();
+const tags = ref();
 const form = reactive({
-    itemName: '',
-    sourceID: '',
-    fromRepo: true,
-    repoID: 1,
+    imageID: '',
+    fromRepo: false,
+    repo: '',
+    originName: '',
     targetName: '',
+
+    deleteTag: false,
+    deleteTags: [],
 });
 
 interface DialogProps {
-    itemName: string;
     repos: Array<Container.RepoOptions>;
-    sourceID: string;
+    imageID: string;
+    tags: Array<string>;
 }
 
 const acceptParams = async (params: DialogProps): Promise<void> => {
     drawerVisible.value = true;
-    form.repoID = 1;
-    form.itemName = params.itemName;
-    form.sourceID = params.sourceID;
-    form.targetName = '';
-    form.fromRepo = true;
+    form.imageID = params.imageID;
+    form.originName = params.tags?.length !== 0 ? params.tags[0] : '';
+    form.targetName = params.tags?.length !== 0 ? params.tags[0] : '';
+    form.fromRepo = false;
+    form.repo = '';
+    form.deleteTag = false;
+    form.deleteTags = [];
     repos.value = params.repos;
+    tags.value = params.tags;
 };
 const emit = defineEmits<{ (e: 'search'): void }>();
 
@@ -91,13 +111,17 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
     if (!formEl) return;
     formEl.validate(async (valid) => {
         if (!valid) return;
-        if (!form.fromRepo) {
-            form.repoID = 0;
-        }
+        let params = {
+            sourceID: form.imageID,
+            targetName: form.targetName,
+        };
         loading.value = true;
-        await imageTag(form)
-            .then(() => {
+        await imageTag(params)
+            .then(async () => {
                 loading.value = false;
+                if (form.deleteTag && form.deleteTags.length !== 0) {
+                    await imageRemove({ names: form.deleteTags });
+                }
                 drawerVisible.value = false;
                 emit('search');
                 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) {
-        if (item.id === id) {
-            return item.downloadUrl;
+        if (item.name == val) {
+            form.targetName = item.downloadUrl + '/' + form.originName;
+            return;
         }
     }
-    return '';
-}
+};
 
 defineExpose({
     acceptParams,
diff --git a/frontend/src/views/toolbox/device/swap/index.vue b/frontend/src/views/toolbox/device/swap/index.vue
index 56927d8ff..48a8061dd 100644
--- a/frontend/src/views/toolbox/device/swap/index.vue
+++ b/frontend/src/views/toolbox/device/swap/index.vue
@@ -147,7 +147,7 @@ const loadData = (path: string) => {
         item.size = itemSize.size;
         item.sizeUnit = itemSize.unit;
     }
-    if (!isExist && path !== '') {
+    if (!isExist) {
         form.swapDetails.push({
             path: path + '/.1panel_swap',
             size: 0,