Merge pull request #10819 from jwangyangls/add-ut-oci

Add copy artifact and update repo info and add ut
This commit is contained in:
jwangyangls 2020-02-25 12:43:04 +08:00 committed by GitHub
commit 3174734473
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 208 additions and 96 deletions

View File

@ -23,7 +23,7 @@
</div>
<div class="modal-footer">
<button type="button"
[disabled]="imageNameInput.projectName.invalid||imageNameInput.repoName.invalid||imageNameInput.tagName.invalid||imageNameInput.noProjectInfo!=''"
[disabled]="imageNameInput.projectName.invalid||imageNameInput.repoName.invalid||imageNameInput.noProjectInfo!=''"
class="btn btn-primary" (click)="onRetag()">{{'BUTTON.CONFIRM' | translate}}</button>
</div>
</clr-modal>
@ -179,11 +179,11 @@
<div>
<div class="inner truncated ">
<span>
{{artifact.tags[0].name | slice:0:5}}
{{artifact?.tags[0]?.name}}
</span>
<span class="eslip"
*ngIf="artifact.tags.length>1||artifact.tags.length>5">...</span>
<span class=""> ({{artifact.tags.length}})</span>
*ngIf="artifact?.tags?.length>1">...</span>
<span *ngIf="artifact?.tags?.length>1" > ({{artifact?.tags?.length}})</span>
</div>
</div>

View File

@ -247,8 +247,6 @@
.retag-modal-body {
overflow-y: hidden;
min-height: 450px;
padding-top: 16px;
}
hbr-image-name-input {

View File

@ -32,14 +32,14 @@ import { CopyInputComponent } from "../../../../../../lib/components/push-image/
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
import { ChannelService } from "../../../../../../lib/services/channel.service";
import { OperationService } from "../../../../../../lib/components/operation/operation.service";
import { By } from "@angular/platform-browser";
import { ArtifactService as NewArtifactService } from "../../../../../../../ng-swagger-gen/services/artifact.service";
describe("ArtifactListTabComponent (inline template)", () => {
let comp: ArtifactListTabComponent;
let fixture: ComponentFixture<ArtifactListTabComponent>;
let artifactService: ArtifactService;
let userPermissionService: UserPermissionService;
let spy: jasmine.Spy;
let spyLabels: jasmine.Spy;
let spyLabels1: jasmine.Spy;
let spyScanner: jasmine.Spy;
@ -88,15 +88,14 @@ describe("ArtifactListTabComponent (inline template)", () => {
name: 'tag1',
artifact_id: 1,
upload_time: '2020-01-06T09:40:08.036866579Z',
},
{
},
{
id: '2',
name: 'tag2',
artifact_id: 2,
pull_time: '2020-01-06T09:40:08.036866579Z',
push_time: '2020-01-06T09:40:08.036866579Z',
}
],
},],
references: [new Reference(1), new Reference(2)],
media_type: 'string',
"digest": "sha256:4875cda368906fd670c9629b5e416ab3d6c0292015f3c3f12ef37dc9a32fc8d4",
@ -137,13 +136,70 @@ describe("ArtifactListTabComponent (inline template)", () => {
}
],
"push_time": "2020-01-07T03:33:41.162319Z",
"pull_time": "0001-01-01T00:00:00Z"
},
{
"id": 1,
type: 'image',
repository: "goharbor/harbor-portal",
tags: [{
id: '1',
name: 'tag1',
artifact_id: 1,
upload_time: '2020-01-06T09:40:08.036866579Z',
},
{
id: '2',
name: 'tag2',
artifact_id: 2,
pull_time: '2020-01-06T09:40:08.036866579Z',
push_time: '2020-01-06T09:40:08.036866579Z',
}
],
references: [new Reference(1), new Reference(2)],
media_type: 'string',
"digest": "sha256:3e33e3e3",
"size": 20372934,
"scan_overview": {
"application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0": {
"report_id": "5e64bc05-3102-11ea-93ae-0242ac140004",
"scan_status": "Error",
"severity": "",
"duration": 118,
"summary": null,
"start_time": "2020-01-07T04:01:23.157711Z",
"end_time": "2020-01-07T04:03:21.662766Z"
}
},
"labels": [
{
"id": 3,
"name": "aaa",
"description": "",
"color": "#0095D3",
"scope": "g",
"project_id": 0,
"creation_time": "2020-01-13T05:44:00.580198Z",
"update_time": "2020-01-13T05:44:00.580198Z",
"deleted": false
},
{
"id": 6,
"name": "dbc",
"description": "",
"color": "",
"scope": "g",
"project_id": 0,
"creation_time": "2020-01-13T08:27:19.279123Z",
"update_time": "2020-01-13T08:27:19.279123Z",
"deleted": false
}
],
"push_time": "2020-01-07T03:33:41.162319Z",
"pull_time": "0001-01-01T00:00:00Z",
hasReferenceArtifactList: [],
noReferenceArtifactList: []
}
}
];
let filtereName = '';
let mockLabels: Label[] = [
{
color: "#9b0d54",
@ -198,17 +254,44 @@ describe("ArtifactListTabComponent (inline template)", () => {
let mockHasDeleteImagePermission: boolean = true;
let mockHasScanImagePermission: boolean = true;
const mockErrorHandler = {
error: () => {}
error: () => { }
};
const permissions = [
{resource: USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.VALUE.CREATE},
{resource: USERSTATICPERMISSION.REPOSITORY.KEY, action: USERSTATICPERMISSION.REPOSITORY.VALUE.PULL},
{resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE},
{resource: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE},
{ resource: USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.VALUE.CREATE },
{ resource: USERSTATICPERMISSION.REPOSITORY.KEY, action: USERSTATICPERMISSION.REPOSITORY.VALUE.PULL },
{ resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE },
{ resource: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE },
];
const mockRouter = {
navigate: () => { }
};
const mockOperationService = {
publishInfo: () => {}
};
const mockArtifactService = {
getArtifactList: () => {
if (filtereName === 'sha256:3e33e3e3') {
return of(
{
body: [mockArtifacts[1]]
}
);
} else {
return of(
{
body: mockArtifacts
}
).pipe(delay(0));
}
},
TriggerArtifactChan$: {
subscribe: (fn) => {
}
},
deleteArtifact: () => of (null)
}
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
@ -232,7 +315,7 @@ describe("ArtifactListTabComponent (inline template)", () => {
ArtifactDefaultService,
{ provide: Router, useValue: mockRouter },
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: ArtifactService, useClass: ArtifactDefaultService },
{ provide: ArtifactService, useValue: mockArtifactService },
{ provide: ProjectService, useClass: ProjectDefaultService },
{ provide: RetagService, useClass: RetagDefaultService },
{ provide: ScanningResultService, useClass: ScanningResultDefaultService },
@ -240,7 +323,8 @@ describe("ArtifactListTabComponent (inline template)", () => {
{ provide: UserPermissionService, useClass: UserPermissionDefaultService },
{ provide: ErrorHandler, useValue: mockErrorHandler },
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
{ provide: OperationService },
{ provide: OperationService, useValue: mockOperationService },
{ provide: NewArtifactService, useValue: null },
]
}).compileComponents();
}));
@ -257,28 +341,64 @@ describe("ArtifactListTabComponent (inline template)", () => {
comp.withNotary = false;
comp.withAdmiral = false;
let labelService: LabelService;
artifactService = fixture.debugElement.injector.get(ArtifactService);
spy = spyOn(artifactService, "getArtifactList").and.returnValues(of(
{
body: mockArtifacts
}
).pipe(delay(0)));
userPermissionService = fixture.debugElement.injector.get(UserPermissionService);
let http: HttpClient;
http = fixture.debugElement.injector.get(HttpClient);
spyScanner = spyOn(http, "get").and.returnValue(of(scannerMock));
spyOn(userPermissionService, "hasProjectPermissions")
.withArgs(comp.projectId, permissions )
.and.returnValue(of([mockHasAddLabelImagePermission, mockHasRetagImagePermission,
mockHasDeleteImagePermission, mockHasScanImagePermission]));
.withArgs(comp.projectId, permissions)
.and.returnValue(of([mockHasAddLabelImagePermission, mockHasRetagImagePermission,
mockHasDeleteImagePermission, mockHasScanImagePermission]));
labelService = fixture.debugElement.injector.get(LabelService);
spyLabels = spyOn(labelService, "getGLabels").and.returnValues(of(mockLabels).pipe(delay(0)));
spyLabels1 = spyOn(labelService, "getPLabels").withArgs(comp.projectId).and.returnValues(of(mockLabels1).pipe(delay(0)));
fixture.detectChanges();
});
it("should load data", async(() => {
expect(spy.calls.any).toBeTruthy();
}));
it("should load and render data", () => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let de: DebugElement = fixture.debugElement.query(del => del.classes["datagrid-cell"]);
fixture.detectChanges();
expect(de).toBeTruthy();
let el: HTMLElement = de.nativeElement;
expect(el).toBeTruthy();
expect(el.textContent.trim()).toEqual("sha256:4875cda3");
});
});
it('should filter data by keyword', async () => {
fixture.detectChanges();
await fixture.whenStable();
filtereName = 'sha256:3e33e3e3';
comp.doSearchArtifactNames('sha256:3e33e3e3');
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
let de: DebugElement = fixture.debugElement.query(del => del.classes["datagrid-cell"]);
expect(de).toBeTruthy();
let el: HTMLElement = de.nativeElement;
expect(el).toBeTruthy();
filtereName = '';
expect(el.textContent.trim()).toEqual('sha256:3e33e3e3');
});
it('should delete artifact', async () => {
fixture.detectChanges();
await fixture.whenStable();
comp.selectedRow = [mockArtifacts[0]];
filtereName = 'sha256:3e33e3e3';
comp.confirmDeletion({source: 9, state: 1, data: comp.selectedRow});
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
let de: DebugElement = fixture.debugElement.query(del => del.classes["datagrid-cell"]);
expect(de).toBeTruthy();
let el: HTMLElement = de.nativeElement;
expect(el).toBeTruthy();
filtereName = '';
expect(el.textContent.trim()).toEqual('sha256:3e33e3e3');
});
});

View File

@ -51,7 +51,7 @@ import {
import { ImageNameInputComponent } from "../../../../../../lib/components/image-name-input/image-name-input.component";
import { CopyInputComponent } from "../../../../../../lib/components/push-image/copy-input.component";
import { ErrorHandler } from "../../../../../../lib/utils/error-handler";
import { ArtifactDefaultService } from "../../../artifact/artifact.service";
import { ArtifactService } from "../../../artifact/artifact.service";
import { OperationService } from "../../../../../../lib/components/operation/operation.service";
import { ChannelService } from "../../../../../../lib/services/channel.service";
import {
@ -63,7 +63,7 @@ import { operateChanges, OperateInfo, OperationState } from "../../../../../../l
import { errorHandler } from "../../../../../../lib/utils/shared/shared.utils";
import { Artifact } from "../../../artifact/artifact";
import { Project } from "../../../../project";
import { ArtifactService as NewArtifactService } from "../../../../../../../ng-swagger-gen/services/artifact.service";
export interface LabelState {
iconsShow: boolean;
label: Label;
@ -165,7 +165,8 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
private retagService: RetagService,
private userPermissionService: UserPermissionService,
private labelService: LabelService,
private artifactService: ArtifactDefaultService,
private artifactService: ArtifactService,
private newArtifactService: NewArtifactService,
private translateService: TranslateService,
private operationService: OperationService,
private channel: ChannelService,
@ -636,19 +637,16 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
if (this.selectedRow && this.selectedRow.length) {
this.retagDialogOpened = true;
this.retagSrcImage = this.repoName + ":" + this.selectedRow[0].digest;
} else {
this.errorHandlerService.error("One tag should be selected before retag.");
}
}
onRetag() {
this.retagService.retag({
targetProject: this.imageNameInput.projectName.value,
targetRepo: this.imageNameInput.repoName.value,
targetTag: this.imageNameInput.tagName.value,
srcImage: this.retagSrcImage,
override: true
})
let params: NewArtifactService.CopyArtifactParams = {
projectName: this.imageNameInput.projectName.value,
repositoryName: this.imageNameInput.repoName.value,
from: `${this.projectName}/${this.repoName}@${this.selectedRow[0].digest}`,
};
this.newArtifactService.CopyArtifact(params)
.pipe(finalize(() => {
this.retagDialogOpened = false;
this.imageNameInput.form.reset();
@ -656,16 +654,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
.subscribe(response => {
this.translateService.get('RETAG.MSG_SUCCESS').subscribe((res: string) => {
this.errorHandlerService.info(res);
if (`${this.imageNameInput.projectName.value}/${this.imageNameInput.repoName.value}` === this.repoName) {
let st: State = this.currentState;
if (!st) {
st = { page: {} };
}
st.page.size = this.pageSize;
st.page.from = 0;
st.page.to = this.pageSize - 1;
this.clrLoad(st);
}
});
}, error => {
this.errorHandlerService.error(error);
@ -701,7 +689,17 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
&& message.state === ConfirmationState.CONFIRMED) {
let artifactList = message.data;
if (artifactList && artifactList.length) {
this.findArtifactFromIndex(artifactList);
artifactList.forEach(artifact => {
this.deleteArtifactobservableLists.push(this.delOperate(artifact));
});
forkJoin(...this.deleteArtifactobservableLists).subscribe((items) => {
// if delete one success refresh list
if (items.some(item => !item)) {
this.selectedRow = [];
let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} };
this.clrLoad(st);
}
});
}
}
}

View File

@ -21,7 +21,9 @@ import { ErrorHandler } from "../../../../../lib/utils/error-handler";
import { HttpClientTestingModule } from "@angular/common/http/testing";
import { IServiceConfig, SERVICE_CONFIG } from "../../../../../lib/entities/service.config";
import { SharedModule } from "../../../../../lib/utils/shared/shared.module";
import {
RepositoryService as NewRepositoryService
} from "../../../../../../ng-swagger-gen/services/repository.service";
describe('ArtifactListComponent (inline template)', () => {
@ -44,7 +46,8 @@ describe('ArtifactListComponent (inline template)', () => {
subscribe: () => {
return of(null);
}
}
},
snapshot: { data: null }
};
let mockChannelService = {
scanCommand$: of(1)
@ -81,7 +84,9 @@ describe('ArtifactListComponent (inline template)', () => {
'tags_count': 1
}
];
let newRepositoryService = {
updateRepository: () => of(null)
};
let mockRepo: Repository = {
metadata: { xTotalCount: 2 },
data: mockRepoData
@ -119,6 +124,7 @@ describe('ArtifactListComponent (inline template)', () => {
{ provide: ArtifactService, useClass: ArtifactDefaultService },
{ provide: ActivatedRoute, useValue: mockActivatedRoute },
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: NewRepositoryService, useValue: newRepositoryService},
]
});
}));

View File

@ -24,7 +24,10 @@ import { RepositoryService } from "../../repository.service";
import { ArtifactService } from "../../artifact/artifact.service";
import { ConfirmationState, ConfirmationTargets } from "../../../../../lib/entities/shared.const";
import { ActivatedRoute } from "@angular/router";
import { Project } from '../../../project';
import {
RepositoryService as NewRepositoryService
} from "../../../../../../ng-swagger-gen/services/repository.service";
const TabLinkContentMap: { [index: string]: string } = {
'repo-info': 'info',
'repo-image': 'image'
@ -58,7 +61,7 @@ export class ArtifactListComponent implements OnInit {
orgImageInfo: string;
timerHandler: any;
projectName: string = '';
@ViewChild('confirmationDialog', { static: false })
confirmationDlg: ConfirmationDialogComponent;
showCurrentTitle: string;
@ -68,6 +71,7 @@ export class ArtifactListComponent implements OnInit {
private repositoryService: RepositoryService,
private systemInfoService: SystemInfoService,
private artifactService: ArtifactService,
private newRepositoryService: NewRepositoryService,
private translate: TranslateService,
private activatedRoute: ActivatedRoute,
) {
@ -96,6 +100,11 @@ export class ArtifactListComponent implements OnInit {
this.errorHandler.error('Project ID cannot be unset.');
return;
}
const resolverData = this.activatedRoute.snapshot.data;
if (resolverData) {
const pro: Project = <Project>resolverData['projectResolver'];
this.projectName = pro.name;
}
this.showCurrentTitle = this.repoName || 'null';
this.retrieve();
this.inProgress = false;
@ -159,7 +168,12 @@ export class ArtifactListComponent implements OnInit {
return;
}
this.onGoing = true;
this.repositoryService.updateRepositoryDescription(this.repoName, this.imageInfo)
let params: NewRepositoryService.UpdateRepositoryParams = {
repositoryName: this.repoName,
repository: {description: this.imageInfo},
projectName: this.projectName,
};
this.newRepositoryService.updateRepository(params)
.subscribe(() => {
this.onGoing = false;
this.translate.get('CONFIG.SAVE_SUCCESS').subscribe((res: string) => {

View File

@ -1151,7 +1151,7 @@
"MSG_SCHEDULE_RESET": "Garbage Collection schedule has been reset"
},
"RETAG": {
"MSG_SUCCESS": "Retag successfully",
"MSG_SUCCESS": "Copy artifact 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}`)"
},

View File

@ -1148,7 +1148,7 @@
"MSG_SCHEDULE_RESET": "Garbage Collection schedule has been reset"
},
"RETAG": {
"MSG_SUCCESS": "Retag successfully",
"MSG_SUCCESS": "Copy artifact 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}`)"
},

View File

@ -1118,7 +1118,7 @@
"MSG_SCHEDULE_RESET": "Garbage Collection schedule has been reset"
},
"RETAG": {
"MSG_SUCCESS": "Retag successfully",
"MSG_SUCCESS": "Copy artifact 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}`)"
},

View File

@ -1146,7 +1146,7 @@
"MSG_SCHEDULE_RESET": "Agendamento de Garbage Collection foi redefinido"
},
"RETAG": {
"MSG_SUCCESS": "Retag successfully",
"MSG_SUCCESS": "Copy artifact 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}`)"
},

View File

@ -1150,7 +1150,7 @@
"MSG_SCHEDULE_RESET": "Çöp Toplama programı sıfırlandı"
},
"RETAG": {
"MSG_SUCCESS": "Başarıyla yeniden etiketle",
"MSG_SUCCESS": "Copy artifact successfully",
"TIP_REPO": "Bir depo adı yol bileşenlerine bölünmüştür. Depo adının bir bileşeni, isteğe bağlı olarak nokta, kısa çizgi veya alt çizgi ile ayrılmış en az bir küçük harf, alfa sayısal karakterler olmalıdır. Daha kesin olarak, [a-z0-9] + (?: [._-] [a-z0-9] +) * normal ifadesiyle eşleşmelidir. Eğer bir depo adı iki veya daha fazla yol bileşenine sahipse, eğik çizgi ile ayrılmış ('/').Eğik çizgi içeren bir depo adının toplam uzunluğu, 256 karakterden az olmalıdır.",
"TIP_TAG": "Etiket, bir depodaki Docker imajına uygulanan bir etikettir. Etiketler, bir depodaki çeşitli imajların birbirlerinden nasıl ayırt edildikleridir. Regex ile eşleşmesi gerekir: (`[\\ w] [\\ w .-] {0,127}`)"
},

View File

@ -1147,7 +1147,7 @@
"MSG_SCHEDULE_RESET": "垃圾回收定时任务已被重置"
},
"RETAG": {
"MSG_SUCCESS": "Tag 拷贝成功",
"MSG_SUCCESS": "Artifact 拷贝成功",
"TIP_REPO": "镜像仓库名被分解为路径组件。仓库名必须至少有一个小写字母、字母数字字符,可选句点、破折号或下划线分隔。严格意义上说,它必须匹配正则表达式[a-z0-9]+(?[.-][a-z0-9]+)*.如果仓库名有两个或多个路径组件,则它们必须用正斜杠('/')分隔。包括斜杠在内的仓库名的总长度必须小于256个字符。",
"TIP_TAG": "Tag 是应用于存储库中的 Docker 映像的一种标签,它用于区分多种镜像。它需要匹配 Regex([\\w][\\w.-]{0,127})"
},

View File

@ -36,16 +36,4 @@
</clr-control-error>
</div>
</div>
<div class="clr-form-control">
<label class="required clr-control-label">{{ 'REPOSITORY.TAG' | translate }}</label>
<div class="clr-control-container" [class.clr-error]="tagName.invalid && (tagName.dirty || tagName.touched)">
<div class="clr-input-wrapper">
<input type="text" id="tag-name" class="clr-input w-90" formControlName="tagName" autocomplete="off" />
<clr-icon class="clr-validate-icon" shape="exclamation-circle"></clr-icon>
</div>
<clr-control-error *ngIf="tagName.invalid && (tagName.dirty || tagName.touched)" class="tooltip-content">
{{ 'RETAG.TIP_TAG' | translate }}
</clr-control-error>
</div>
</div>
</form>

View File

@ -1,7 +1,3 @@
.clr-form {
margin-top:30px;
width:100%;
}
.select-box {
position: absolute;

View File

@ -35,11 +35,7 @@ export class ImageNameInputComponent implements OnInit, OnDestroy {
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}$')
])],
])]
});
}
@ -96,10 +92,6 @@ export class ImageNameInputComponent implements OnInit, OnDestroy {
return this.imageNameForm.get("repoName");
}
get tagName(): AbstractControl {
return this.imageNameForm.get("tagName");
}
ngOnDestroy(): void {
if (this.proNameChecker) {
this.proNameChecker.unsubscribe();