mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-23 10:45:45 +01:00
show retag tooltip use regex, reset the dialog form when retagged
Signed-off-by: Meina Zhou <meinaz@vmware.com>
This commit is contained in:
parent
70016cc0eb
commit
a3e357bb7f
@ -4,8 +4,13 @@
|
||||
<div class="clr-control-container clr-col-xs-12 clr-col-md-8">
|
||||
<div class="clr-input-wrapper" (mouseleave)="leaveProjectInput()">
|
||||
<label aria-haspopup="true" role="tooltip" class="wrap-label tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='noProjectInfo'>
|
||||
<input type="text" id="project-name" (keyup)='validateProjectName()' (blur)='blurProjectInput()' class="clr-input" formControlName="projectName" required minlength="2" pattern="^[a-z0-9]+(?:[._-][a-z0-9]+)*$" />
|
||||
<span class="tooltip-content">{{noProjectInfo | translate}}</span>
|
||||
<input type="text"
|
||||
id="project-name"
|
||||
class="clr-input"
|
||||
(keyup)='validateProjectName()'
|
||||
(blur)='blurProjectInput()'
|
||||
formControlName="projectName"/>
|
||||
<span *ngIf="noProjectInfo && (projectName.dirty || projectName.touched)" class="tooltip-content">{{noProjectInfo | translate}}</span>
|
||||
</label>
|
||||
<div class="select-box" [style.display]="selectedProjectList.length ? 'block' : 'none'">
|
||||
<ul>
|
||||
@ -20,8 +25,8 @@
|
||||
<div class="clr-control-container clr-col-xs-12 clr-col-md-8">
|
||||
<div class="clr-input-wrapper">
|
||||
<label aria-haspopup="true" role="tooltip" class="wrap-label tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='repoName.invalid && (repoName.dirty || repoName.touched)'>
|
||||
<input type="text" id="repo-name" class="clr-input" formControlName="repoName" required />
|
||||
<span *ngIf="repoName.invalid && (repoName.dirty || repoName.touched)" class="tooltip-content">{{ 'TOOLTIP.NONEMPTY' | translate }}</span>
|
||||
<input type="text" id="repo-name" class="clr-input" formControlName="repoName" />
|
||||
<span *ngIf="repoName.invalid && (repoName.dirty || repoName.touched)" class="tooltip-content">{{ 'RETAG.TIP_REPO' | translate }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -31,8 +36,8 @@
|
||||
<div class="clr-control-container clr-col-xs-12 clr-col-md-8">
|
||||
<div class="clr-input-wrapper">
|
||||
<label aria-haspopup="true" role="tooltip" class="wrap-label tooltip tooltip-validation tooltip-md tooltip-bottom-left" [class.invalid]='tagName.invalid && (tagName.dirty || tagName.touched)'>
|
||||
<input type="text" id="tag-name" class="clr-input" formControlName="tagName" required />
|
||||
<span *ngIf="repoName.invalid && (repoName.dirty || repoName.touched)" class="tooltip-content">{{ 'TOOLTIP.NONEMPTY' | translate }}</span>
|
||||
<input type="text" id="tag-name" class="clr-input" formControlName="tagName" />
|
||||
<span *ngIf="tagName.invalid && (tagName.dirty || tagName.touched)" class="tooltip-content">{{ 'RETAG.TIP_TAG' | translate }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Project } from "../project-policy-config/project";
|
||||
import { Observable, Subject } from "rxjs/index";
|
||||
import { Subject } from "rxjs/index";
|
||||
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
|
||||
import { ProjectService } from "../service/project.service";
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
@ -16,6 +16,9 @@ export class ImageNameInputComponent implements OnInit, OnDestroy {
|
||||
selectedProjectList: Project[] = [];
|
||||
proNameChecker: Subject<string> = new Subject<string>();
|
||||
imageNameForm: FormGroup;
|
||||
public project: string;
|
||||
public repo: string;
|
||||
public tag: string;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
@ -23,9 +26,20 @@ export class ImageNameInputComponent implements OnInit, OnDestroy {
|
||||
private proService: ProjectService,
|
||||
) {
|
||||
this.imageNameForm = this.fb.group({
|
||||
projectName: ["", Validators.required],
|
||||
repoName: ["", Validators.required],
|
||||
tagName: ["", Validators.required],
|
||||
projectName: ["", Validators.compose([
|
||||
Validators.minLength(2),
|
||||
Validators.required,
|
||||
Validators.pattern('^[a-z0-9]+(?:[._-][a-z0-9]+)*$')
|
||||
])],
|
||||
repoName: ["", Validators.compose([
|
||||
Validators.required,
|
||||
Validators.maxLength(256),
|
||||
Validators.pattern('^[a-z0-9]+(?:[._-][a-z0-9]+)*(/[a-z0-9]+(?:[._-][a-z0-9]+)*)*')
|
||||
])],
|
||||
tagName: ["", Validators.compose([
|
||||
Validators.required,
|
||||
Validators.pattern('^[\\w][\\w.-]{0,127}$')
|
||||
])],
|
||||
});
|
||||
}
|
||||
ngOnInit(): void {
|
||||
@ -60,6 +74,23 @@ export class ImageNameInputComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
validateProjectName(): void {
|
||||
let cont = this.imageNameForm.controls["projectName"];
|
||||
if (cont && cont.valid) {
|
||||
this.proNameChecker.next(cont.value);
|
||||
} else {
|
||||
this.noProjectInfo = "PROJECT.NAME_TOOLTIP";
|
||||
}
|
||||
}
|
||||
|
||||
blurProjectInput(): void {
|
||||
this.validateProjectName();
|
||||
}
|
||||
|
||||
get form(): AbstractControl {
|
||||
return this.imageNameForm;
|
||||
}
|
||||
|
||||
get projectName(): AbstractControl {
|
||||
return this.imageNameForm.get("projectName");
|
||||
}
|
||||
@ -78,19 +109,6 @@ export class ImageNameInputComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
validateProjectName(): void {
|
||||
let cont = this.imageNameForm.controls["projectName"];
|
||||
if (cont && cont.valid) {
|
||||
this.proNameChecker.next(cont.value);
|
||||
} else {
|
||||
this.noProjectInfo = "PROJECT.NAME_TOOLTIP";
|
||||
}
|
||||
}
|
||||
|
||||
blurProjectInput(): void {
|
||||
this.validateProjectName();
|
||||
}
|
||||
|
||||
leaveProjectInput(): void {
|
||||
this.selectedProjectList = [];
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
<button type="button" class="btn btn-primary" [ngxClipboard]="digestTarget" (cbOnSuccess)="onSuccess($event)" (cbOnError)="onError($event)">{{'BUTTON.COPY' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<clr-modal class="hidden-tag" [(clrModalOpen)]="retagDialogOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="retagDialogClosable">
|
||||
<clr-modal class="hidden-tag" [(clrModalOpen)]="retagDialogOpened" [clrModalStaticBackdrop]="staticBackdrop">
|
||||
<h3 class="modal-title">{{ 'REPOSITORY.RETAG' | translate }}</h3>
|
||||
<div class="modal-body retag-modal-body">
|
||||
<div class="row col-md-12">
|
||||
|
@ -202,7 +202,7 @@
|
||||
|
||||
.retag-modal-body {
|
||||
overflow-y: hidden;
|
||||
min-height: 184px;
|
||||
min-height: 450px;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
ElementRef, AfterViewInit
|
||||
} from "@angular/core";
|
||||
import {Subject, forkJoin} from "rxjs";
|
||||
import { debounceTime , distinctUntilChanged} from 'rxjs/operators';
|
||||
import { debounceTime , distinctUntilChanged, finalize} from 'rxjs/operators';
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { State, Comparator } from "@clr/angular";
|
||||
|
||||
@ -97,7 +97,6 @@ export class TagComponent implements OnInit, AfterViewInit {
|
||||
digestId: string;
|
||||
staticBackdrop = true;
|
||||
closable = false;
|
||||
retagDialogClosable = true;
|
||||
lastFilteredTagName: string;
|
||||
inprogress: boolean;
|
||||
openLabelFilterPanel: boolean;
|
||||
@ -587,14 +586,21 @@ export class TagComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
onRetag() {
|
||||
this.retagDialogOpened = false;
|
||||
this.retagService.retag({
|
||||
targetProject: this.imageNameInput.projectName.value,
|
||||
targetRepo: this.imageNameInput.repoName.value,
|
||||
targetTag: this.imageNameInput.tagName.value,
|
||||
srcImage: this.retagSrcImage,
|
||||
override: true
|
||||
}).subscribe(response => {
|
||||
})
|
||||
.pipe(finalize(() => {
|
||||
this.retagDialogOpened = false;
|
||||
this.imageNameInput.form.reset();
|
||||
}))
|
||||
.subscribe(response => {
|
||||
this.translateService.get('RETAG.MSG_SUCCESS').subscribe((res: string) => {
|
||||
this.errorHandler.info(res);
|
||||
});
|
||||
}, error => {
|
||||
this.errorHandler.error(error);
|
||||
});
|
||||
|
@ -69,7 +69,7 @@
|
||||
"RULE_USER_EXISTING": "Name is already in use.",
|
||||
"EMPTY": "Name is required",
|
||||
"NONEMPTY": "Can't be empty",
|
||||
"REPO_TOOLTIP": "Users can not do any operations to the images in this mode."
|
||||
"REPO_TOOLTIP": "Users can not do any operations to the images in this mode.",
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"CURRENT_PWD": "Enter current password",
|
||||
@ -856,6 +856,11 @@
|
||||
"MSG_SUCCESS": "Garbage Collection Successful",
|
||||
"MSG_SCHEDULE_SET": "Garbage Collection schedule has been set",
|
||||
"MSG_SCHEDULE_RESET": "Garbage Collection schedule has been reset"
|
||||
},
|
||||
"RETAG": {
|
||||
"MSG_SUCCESS": "Retag successfully",
|
||||
"TIP_REPO": "A repository name is broken up into path components. A component of a repository name must be at least one lowercase, alpha-numeric characters, optionally separated by periods, dashes or underscores. More strictly, it must match the regular expression [a-z0-9]+(?:[._-][a-z0-9]+)*.If a repository name has two or more path components, they must be separated by a forward slash ('/').The total length of a repository name, including slashes, must be less the 256 characters.",
|
||||
"TIP_TAG": "A tag is a label applied to a Docker image in a repository. Tags are how various images in a repository are distinguished from each other.It need to match Regex: (`[\\w][\\w.-]{0,127}`)"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -853,5 +853,11 @@
|
||||
"MSG_SUCCESS": "Garbage Collection Successful",
|
||||
"MSG_SCHEDULE_SET": "Garbage Collection schedule has been set",
|
||||
"MSG_SCHEDULE_RESET": "Garbage Collection schedule has been reset"
|
||||
},
|
||||
"RETAG": {
|
||||
"MSG_SUCCESS": "Retag successfully",
|
||||
"TIP_REPO": "A repository name is broken up into path components. A component of a repository name must be at least one lowercase, alpha-numeric characters, optionally separated by periods, dashes or underscores. More strictly, it must match the regular expression [a-z0-9]+(?:[._-][a-z0-9]+)*.If a repository name has two or more path components, they must be separated by a forward slash ('/').The total length of a repository name, including slashes, must be less the 256 characters.",
|
||||
"TIP_TAG": "A tag is a label applied to a Docker image in a repository. Tags are how various images in a repository are distinguished from each other.It need to match Regex: (`[\\w][\\w.-]{0,127}`)"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -816,5 +816,11 @@
|
||||
"MSG_SUCCESS": "Garbage Collection Successful",
|
||||
"MSG_SCHEDULE_SET": "Garbage Collection schedule has been set",
|
||||
"MSG_SCHEDULE_RESET": "Garbage Collection schedule has been reset"
|
||||
},
|
||||
"RETAG": {
|
||||
"MSG_SUCCESS": "Retag successfully",
|
||||
"TIP_REPO": "A repository name is broken up into path components. A component of a repository name must be at least one lowercase, alpha-numeric characters, optionally separated by periods, dashes or underscores. More strictly, it must match the regular expression [a-z0-9]+(?:[._-][a-z0-9]+)*.If a repository name has two or more path components, they must be separated by a forward slash ('/').The total length of a repository name, including slashes, must be less the 256 characters.",
|
||||
"TIP_TAG": "A tag is a label applied to a Docker image in a repository. Tags are how various images in a repository are distinguished from each other.It need to match Regex: (`[\\w][\\w.-]{0,127}`)"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -847,6 +847,12 @@
|
||||
"MSG_SUCCESS":"Garbage Collection efetuado com sucesso",
|
||||
"MSG_SCHEDULE_SET":"Agendamento de Garbage Collection efetuado",
|
||||
"MSG_SCHEDULE_RESET":"Agendamento de Garbage Collection foi redefinido"
|
||||
},
|
||||
"RETAG": {
|
||||
"MSG_SUCCESS": "Retag successfully",
|
||||
"TIP_REPO": "A repository name is broken up into path components. A component of a repository name must be at least one lowercase, alpha-numeric characters, optionally separated by periods, dashes or underscores. More strictly, it must match the regular expression [a-z0-9]+(?:[._-][a-z0-9]+)*.If a repository name has two or more path components, they must be separated by a forward slash ('/').The total length of a repository name, including slashes, must be less the 256 characters.",
|
||||
"TIP_TAG": "A tag is a label applied to a Docker image in a repository. Tags are how various images in a repository are distinguished from each other.It need to match Regex: (`[\\w][\\w.-]{0,127}`)"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -851,5 +851,11 @@
|
||||
"MSG_SUCCESS": "垃圾回收成功",
|
||||
"MSG_SCHEDULE_SET": "垃圾回收定时任务设置成功",
|
||||
"MSG_SCHEDULE_RESET": "垃圾回收定时任务已被重置"
|
||||
},
|
||||
"RETAG": {
|
||||
"MSG_SUCCESS": "复制成功",
|
||||
"TIP_REPO": "镜像仓库名被分解为路径组件。仓库名必须至少有一个小写字母、字母数字字符,可选句点、破折号或下划线分隔。严格意义上说,它必须匹配正则表达式[a-z0-9]+(?[.-][a-z0-9]+)*.如果仓库名有两个或多个路径组件,则它们必须用正斜杠('/')分隔。包括斜杠在内的仓库名的总长度必须小于256个字符。",
|
||||
"TIP_TAG": "标签是应用于存储库中的Docker映像的一种标签,它用于区分多种镜像。它需要匹配Regex:([\\w][\\w.-]{0,127})"
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user