Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions agent/app/dto/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ type ImagePull struct {
}

type ImageTag struct {
SourceID string `json:"sourceID" validate:"required"`
TargetName string `json:"targetName" validate:"required"`
SourceID string `json:"sourceID" validate:"required"`
Tags []string `json:"tags" validate:"required"`
}

type ImagePush struct {
Expand Down
32 changes: 31 additions & 1 deletion agent/app/service/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,39 @@ func (u *ImageService) ImageTag(req dto.ImageTag) error {
}
defer client.Close()

if err := client.ImageTag(context.TODO(), req.SourceID, req.TargetName); err != nil {
imageItem, err := client.ImageInspect(context.Background(), req.SourceID)
if err != nil {
return err
}

for _, tag := range req.Tags {
isNew := true
for _, tagOld := range imageItem.RepoTags {
if tag == tagOld {
isNew = false
break
}
}
if isNew {
if err := client.ImageTag(context.TODO(), req.SourceID, tag); err != nil {
return err
}
}
}
for _, tagOld := range imageItem.RepoTags {
isDel := true
for _, tag := range req.Tags {
if tag == tagOld {
isDel = false
break
}
}
if isDel {
if _, err := client.ImageRemove(context.TODO(), tagOld, image.RemoveOptions{}); err != nil {
return err
}
}
}
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/interface/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export namespace Container {
}
export interface ImageTag {
sourceID: string;
targetName: string;
tags: Array<string>;
}
export interface ImagePush {
taskID: string;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -882,7 +882,6 @@ const message = {
imagePushHelper:
'Detected that this image has multiple tags. Please confirm that the image name used for pushing is: {0}',
imageDelete: 'Image delete',
imageTagDeleteHelper: 'Remove other tags associated with this image ID',
repoName: 'Container registry',
imageName: 'Image name',
pull: 'Pull',
Expand All @@ -893,6 +892,7 @@ const message = {
pathSelect: 'Path',
label: 'Label',
imageTag: 'Image tag',
imageTagHelper: 'Supports setting multiple image tags, press Enter after entering each tag to continue',
push: 'Push',
fileName: 'Filename',
export: 'Export',
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/lang/modules/es-es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,6 @@ const message = {
imagePushHelper:
'Detected that this image has multiple tags. Please confirm that the image name used for pushing is: {0}',
imageDelete: 'Eliminar imagen',
imageTagDeleteHelper: 'Eliminar otras etiquetas asociadas con este ID de imagen',
repoName: 'Repositorio de contenedores',
imageName: 'Nombre de la imagen',
pull: 'Descargar',
Expand All @@ -895,6 +894,8 @@ const message = {
pathSelect: 'Ruta',
label: 'Etiqueta',
imageTag: 'Etiqueta de imagen',
imageTagHelper:
'Admite configurar múltiples etiquetas de imagen, presione Enter después de ingresar cada etiqueta para continuar',
push: 'Subir',
fileName: 'Nombre de archivo',
export: 'Exportar',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lang/modules/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,6 @@ const message = {
imagePushHelper:
'このイメージに複数のタグが存在することが検出されました。プッシュ時に使用するイメージ名が以下であることを確認してください:{0}',
imageDelete: '画像削除',
imageTagDeleteHelper: 'この画像IDに関連付けられた他のタグを削除します',
repoName: 'コンテナレジストリ',
imageName: '画像名',
pull: '引く',
Expand All @@ -869,6 +868,7 @@ const message = {
pathSelect: 'パス',
label: 'ラベル',
imageTag: '画像タグ',
imageTagHelper: '複数のイメージタグの設定をサポートし、各タグ入力後にEnterキーを押して続行します',
push: '押す',
fileName: 'ファイル名',
export: '輸出',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lang/modules/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -850,7 +850,6 @@ const message = {
imagePushHelper:
'이 이미지에 여러 태그가 있는 것으로 감지되었습니다. 푸시 시 사용할 이미지 이름이 다음인지 확인하세요: {0}',
imageDelete: '이미지 삭제',
imageTagDeleteHelper: '이 이미지 ID와 관련된 다른 태그를 제거합니다.',
repoName: '컨테이너 저장소 이름',
imageName: '이미지 이름',
pull: '풀',
Expand All @@ -861,6 +860,7 @@ const message = {
pathSelect: '경로',
label: '레이블',
imageTag: '이미지 태그',
imageTagHelper: '여러 이미지 태그 설정을 지원하며, 각 태그 입력 후 Enter 키를 눌러 계속합니다',
push: '푸시',
fileName: '파일 이름',
export: '내보내기',
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/lang/modules/ms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,6 @@ const message = {
imagePushHelper:
'Terdapat pengesahan bahawa imej ini mempunyai beberapa tag. Sila pastikan nama imej yang digunakan untuk menolak adalah: {0}',
imageDelete: 'Padam imej',
imageTagDeleteHelper: 'Buang tag lain yang berkaitan dengan ID imej ini',
repoName: 'Pendaftaran kontena',
imageName: 'Nama imej',
pull: 'Tarik',
Expand All @@ -886,6 +885,8 @@ const message = {
pathSelect: 'Laluan',
label: 'Label',
imageTag: 'Tag imej',
imageTagHelper:
'Menyokong penetapan berbilang tag imej, tekan Enter selepas memasukkan setiap tag untuk teruskan',
push: 'Tekan',
fileName: 'Nama fail',
export: 'Eksport',
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/lang/modules/pt-br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,6 @@ const message = {
imagePushHelper:
'Detectado que esta imagem possui múltiplas tags. Por favor, confirme que o nome da imagem usada para push é: {0}',
imageDelete: 'Excluir imagem',
imageTagDeleteHelper: 'Remover outras tags associadas a este ID de imagem',
repoName: 'Registro de contêiner',
imageName: 'Nome da imagem',
pull: 'Puxar',
Expand All @@ -881,6 +880,8 @@ const message = {
pathSelect: 'Caminho',
label: 'Etiqueta',
imageTag: 'Tag de imagem',
imageTagHelper:
'Suporta definir múltiplas tags de imagem, pressione Enter após inserir cada tag para continuar',
push: 'Enviar',
fileName: 'Nome do arquivo',
export: 'Exportar',
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/lang/modules/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,6 @@ const message = {
imagePushHelper:
'Обнаружено, что у этого образа несколько тегов. Подтвердите, что имя образа, используемое для отправки: {0}',
imageDelete: 'Удалить образ',
imageTagDeleteHelper: 'Удалить другие теги, связанные с этим ID образа',
repoName: 'Реестр контейнеров',
imageName: 'Имя образа',
pull: 'Загрузить',
Expand All @@ -884,6 +883,8 @@ const message = {
pathSelect: 'Путь',
label: 'Метка',
imageTag: 'Тег образа',
imageTagHelper:
'Поддерживает установку нескольких тегов образов, нажмите Enter после ввода каждого тега для продолжения',
push: 'Отправить',
fileName: 'Имя файла',
export: 'Экспорт',
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/lang/modules/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,6 @@ const message = {
imagePushHelper:
'Bu imgenin birden fazla etiketi olduğu tespit edildi. Lütfen gönderimde kullanılan imge adının şu olduğunu onaylayın: {0}',
imageDelete: 'İmaj sil',
imageTagDeleteHelper: 'Bu imaj IDsi ile ilişkili diğer etiketleri kaldır',
repoName: 'Konteyner kayıt defteri',
imageName: 'İmaj adı',
pull: 'Çek',
Expand All @@ -902,6 +901,8 @@ const message = {
pathSelect: 'Yol',
label: 'Etiket',
imageTag: 'İmaj etiketi',
imageTagHelper:
"Birden fazla görüntü etiketi ayarlamayı destekler, her etiket girdikten sonra Enter'a basarak devam edin",
push: 'Gönder',
fileName: 'Dosya adı',
export: 'Dışa aktar',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lang/modules/zh-Hant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,6 @@ const message = {
imagePush: '推送鏡像',
imagePushHelper: '檢測到該映像存在多個標籤,請確認推送時使用的映像名稱為:{0}',
imageDelete: '刪除鏡像',
imageTagDeleteHelper: '移除與該映像 ID 相關聯的其他標籤',
repoName: '倉庫名',
imageName: '鏡像名',
httpRepo: 'http 倉庫新增授信需要重啟 docker 服務',
Expand All @@ -859,6 +858,7 @@ const message = {
pathSelect: '路徑選擇',
label: '標籤',
imageTag: '鏡像標籤',
imageTagHelper: '支援設定多個映像標籤,輸入一個標籤後回車繼續',
push: '推送',
fileName: '檔案名',
export: '匯出',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lang/modules/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,6 @@ const message = {
imagePush: '推送镜像',
imagePushHelper: '检测到该镜像存在多个标签,请确认推送时使用的镜像名称为:{0}',
imageDelete: '删除镜像',
imageTagDeleteHelper: '移除与该镜像 ID 相关联的其他标签',
repoName: '仓库名',
imageName: '镜像名',
httpRepo: 'http 仓库添加授信需要重启 docker 服务',
Expand All @@ -858,6 +857,7 @@ const message = {
pathSelect: '路径选择',
label: '标签',
imageTag: '镜像标签',
imageTagHelper: '支持设置多个镜像 tag,输入一个 tag 后回车继续',
push: '推送',
fileName: '文件名',
export: '导出',
Expand Down
81 changes: 52 additions & 29 deletions frontend/src/views/container/image/tag/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<DrawerPro v-model="drawerVisible" :header="$t('container.imageTag')" @close="handleClose" size="large">
<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" :rules="rules" label-width="80px">
<el-form-item :label="$t('app.source')">
<el-checkbox v-model="form.fromRepo">{{ $t('container.imageRepo') }}</el-checkbox>
</el-form-item>
Expand All @@ -14,19 +14,15 @@
<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.imageTag')" :rules="Rules.imageName" prop="targetName">
<el-input v-model="form.targetName" />
</el-form-item>

<el-form-item>
<div class="w-full">
<el-checkbox v-model="form.deleteTag">
{{ $t('container.imageTagDeleteHelper') }}
</el-checkbox>
</div>
<el-checkbox-group v-if="form.deleteTag" v-model="form.deleteTags">
<el-checkbox v-for="item in tags" :key="item" :value="item" :label="item" />
</el-checkbox-group>
<el-form-item :label="$t('container.imageTag')" prop="tags">
<el-input-tag ref="inputTagRef" @add-tag="handleAdd" v-model="form.tags">
<template #tag="{ value }">
<el-button @click="setInputValue(value)" size="small" link type="info">
{{ value }}
</el-button>
</template>
</el-input-tag>
<span class="input-help">{{ $t('container.imageTagHelper') }}</span>
</el-form-item>
</el-form>

Expand All @@ -48,25 +44,44 @@ import { reactive, ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { imageRemove, imageTag } from '@/api/modules/container';
import { imageTag } from '@/api/modules/container';
import { Container } from '@/api/interface/container';
import { MsgSuccess } from '@/utils/message';

const loading = ref(false);
const inputTagRef = ref();

const drawerVisible = ref(false);
const repos = ref();
const tags = ref();
const form = reactive({
imageID: '',
fromRepo: false,
repo: '',
originName: '',
targetName: '',

deleteTag: false,
deleteTags: [],
tags: [],
});
const rules = reactive({
tags: [{ validator: checkTags, trigger: 'blur', required: true }],
});
function checkTags(rule: any, value: any, callback: any) {
if (value.length === 0) {
return callback(new Error(i18n.global.t('commons.rule.requiredInput')));
}
for (const item of value) {
if (item === '' || typeof item === 'undefined' || item == null) {
return callback(new Error(i18n.global.t('commons.rule.imageName')));
} else {
const reg = /^[a-zA-Z0-9]{1}[a-z:@A-Z0-9_/.-]{0,255}$/;
if (!reg.test(item) && item !== '') {
return callback(new Error(i18n.global.t('commons.rule.imageName')));
} else {
return callback();
}
}
}
callback();
}

interface DialogProps {
repos: Array<Container.RepoOptions>;
Expand All @@ -78,13 +93,10 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
drawerVisible.value = true;
form.imageID = params.imageID;
form.originName = params.tags?.length !== 0 ? params.tags[0] : '';
form.targetName = params.tags?.length !== 0 ? params.tags[0] : '';
form.tags = params.tags || [];
form.fromRepo = false;
form.repo = '';
form.deleteTag = false;
form.deleteTags = [];
repos.value = params.repos;
tags.value = params.tags;
};
const emit = defineEmits<{ (e: 'search'): void }>();

Expand All @@ -95,21 +107,33 @@ const handleClose = () => {
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();

const handleAdd = (val: string) => {
form.tags = form.tags?.filter((item) => item !== val);
form.tags.push(val);
};
const setInputValue = async (text) => {
await nextTick();
const inputEl = inputTagRef.value?.$el?.querySelector('input');
if (!inputEl) return;

inputEl.value = text;
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
inputEl.dispatchEvent(new Event('change', { bubbles: true }));
inputEl.setSelectionRange(text.length, text.length);
};

const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
let params = {
sourceID: form.imageID,
targetName: form.targetName,
tags: form.tags,
};
loading.value = true;
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'));
Expand All @@ -122,12 +146,11 @@ const onSubmit = async (formEl: FormInstance | undefined) => {

const changeRepo = (val) => {
if (val === 'Docker Hub') {
form.targetName = form.originName;
return;
}
for (const item of repos.value) {
if (item.name == val) {
form.targetName = item.downloadUrl + '/' + form.originName;
form.tags.push(item.downloadUrl + '/' + form.originName);
return;
}
}
Expand Down
Loading