diff --git a/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.html.ts b/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.html.ts index 4be3af9c03..86746dc654 100644 --- a/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.html.ts +++ b/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.html.ts @@ -23,7 +23,7 @@ export const REPOSITORY_STACKVIEW_TEMPLATE: string = ` <clr-dg-cell>{{r.name}}</clr-dg-cell> <clr-dg-cell>{{r.tags_count}}</clr-dg-cell> <clr-dg-cell>{{r.pull_count}}</clr-dg-cell> - <hbr-tag *clrIfExpanded ngProjectAs="clr-dg-row-detail" (tagClickEvent)="watchTagClickEvt($event)" class="sub-grid-custom" [repoName]="r.name" [registryUrl]="registryUrl" [withNotary]="withNotary" [withClair]="withClair" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId" [isEmbedded]="true"></hbr-tag> + <hbr-tag *clrIfExpanded ngProjectAs="clr-dg-row-detail" (tagClickEvent)="watchTagClickEvt($event)" (signatureOutput)="saveSignatures($event)" class="sub-grid-custom" [repoName]="r.name" [registryUrl]="registryUrl" [withNotary]="withNotary" [withClair]="withClair" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId" [isEmbedded]="true"></hbr-tag> </clr-dg-row> <clr-dg-footer> <span *ngIf="showDBStatusWarning" class="db-status-warning"> diff --git a/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.ts b/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.ts index 3d02c30003..c8cb663748 100644 --- a/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.ts +++ b/src/ui_ng/lib/src/repository-stackview/repository-stackview.component.ts @@ -6,7 +6,7 @@ import { ViewChild, ChangeDetectionStrategy, ChangeDetectorRef, - EventEmitter + EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { Comparator } from 'clarity-angular'; @@ -41,6 +41,7 @@ import { doFiltering, doSorting } from '../utils'; +import {TagService} from '../service/index'; @Component({ selector: 'hbr-repository-stackview', @@ -48,7 +49,8 @@ import { styles: [REPOSITORY_STACKVIEW_STYLES], changeDetection: ChangeDetectionStrategy.OnPush }) -export class RepositoryStackviewComponent implements OnInit { +export class RepositoryStackviewComponent implements OnChanges, OnInit { + signedCon: {[key: string]: any | string[]} = {}; @Input() projectId: number; @Input() projectName: string = "unknown"; @@ -80,6 +82,8 @@ export class RepositoryStackviewComponent implements OnInit { private translateService: TranslateService, private repositoryService: RepositoryService, private systemInfoService: SystemInfoService, + private translate: TranslateService, + private tagService: TagService, private ref: ChangeDetectorRef) { } public get registryUrl(): string { @@ -122,15 +126,24 @@ export class RepositoryStackviewComponent implements OnInit { } this.translateService.get('REPOSITORY.DELETED_REPO_SUCCESS') .subscribe(res => this.errorHandler.info(res)); - }).catch(error => this.errorHandler.error(error)); + }).catch(error => { + if (error.status === "412"){ + this.translateService.get('REPOSITORY.TAGS_SIGNED') + .subscribe(res => this.errorHandler.info(res)); + return; + } + this.errorHandler.error(error); + }); + } + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['projectId']) { + this.refresh(); } } ngOnInit(): void { - if (!this.projectId) { - this.errorHandler.error('Project ID cannot be unset.'); - return; - } //Get system info for tag views toPromise<SystemInfo>(this.systemInfoService.getSystemInfo()) .then(systemInfo => this.systemInfo = systemInfo) @@ -153,15 +166,67 @@ export class RepositoryStackviewComponent implements OnInit { this.clrLoad(st); } + saveSignatures(event: {[key: string]: string[]}): void { + Object.assign(this.signedCon, event); + } + deleteRepo(repoName: string) { - let message = new ConfirmationMessage( - 'REPOSITORY.DELETION_TITLE_REPO', - 'REPOSITORY.DELETION_SUMMARY_REPO', - repoName, - repoName, - ConfirmationTargets.REPOSITORY, - ConfirmationButtons.DELETE_CANCEL); - this.confirmationDialog.open(message); + // get children tags data + + let signature: string = ''; + if (this.signedCon[repoName]) { + if (this.signedCon[repoName].length === 0) { + this.confirmationDialogSet('DELETION_TITLE_REPO', signature, repoName, 'REPOSITORY.DELETION_SUMMARY_REPO', ConfirmationButtons.DELETE_CANCEL); + return; + } + signature = this.signedCon[repoName].join(','); + this.confirmationDialogSet('DELETION_TITLE_REPO_SIGNED', signature, repoName, 'REPOSITORY.DELETION_SUMMARY_REPO_SIGNED', ConfirmationButtons.CLOSE); + } else { + this.getTagInfo(repoName).then(() => { + if (this.signedCon[repoName].length) { + signature = this.signedCon[repoName].join(','); + this.confirmationDialogSet('DELETION_TITLE_REPO_SIGNED', signature, repoName, 'REPOSITORY.DELETION_SUMMARY_REPO_SIGNED', ConfirmationButtons.CLOSE); + } else { + this.confirmationDialogSet('DELETION_TITLE_REPO', signature, repoName, 'REPOSITORY.DELETION_SUMMARY_REPO', ConfirmationButtons.DELETE_CANCEL); + } + }); + } + } + getTagInfo(repoName: string): Promise<void> { + // this.signedNameArr = []; + this.signedCon[repoName] = []; + return toPromise<Tag[]>(this.tagService + .getTags(repoName)) + .then(items => { + items.forEach((t: Tag) => { + if (t.signature !== null) { + this.signedCon[repoName].push(t.name); + } + }); + }) + .catch(error => this.errorHandler.error(error)); + } + + confirmationDialogSet(summaryTitle: string, signature: string, repoName: string, summaryKey: string, button: ConfirmationButtons): void { + this.translate.get(summaryKey, + { + repoName: repoName, + signedImages: signature, + }) + .subscribe((res: string) => { + summaryKey = res; + let message = new ConfirmationMessage( + summaryTitle, + summaryKey, + repoName, + repoName, + ConfirmationTargets.REPOSITORY, + button); + this.confirmationDialog.open(message); + + let hnd = setInterval(() => this.ref.markForCheck(), 100); + setTimeout(() => clearInterval(hnd), 5000); + }); } refresh() { @@ -194,6 +259,7 @@ export class RepositoryStackviewComponent implements OnInit { this.totalCount = repo.metadata.xTotalCount; this.repositories = repo.data; + this.signedCon = {}; //Do filtering and sorting this.repositories = doFiltering<RepositoryItem>(this.repositories, state); this.repositories = doSorting<RepositoryItem>(this.repositories, state); @@ -231,4 +297,5 @@ export class RepositoryStackviewComponent implements OnInit { return st; } -} \ No newline at end of file +} + diff --git a/src/ui_ng/lib/src/tag/tag.component.ts b/src/ui_ng/lib/src/tag/tag.component.ts index caf46bf3ff..a056e67598 100644 --- a/src/ui_ng/lib/src/tag/tag.component.ts +++ b/src/ui_ng/lib/src/tag/tag.component.ts @@ -71,10 +71,11 @@ export class TagComponent implements OnInit { @Output() refreshRepo = new EventEmitter<boolean>(); @Output() tagClickEvent = new EventEmitter<TagClickEvent>(); + @Output() signatureOutput = new EventEmitter<any>(); + tags: Tag[]; - showTagManifestOpened: boolean; manifestInfoTitle: string; digestId: string; @@ -136,6 +137,7 @@ export class TagComponent implements OnInit { retrieve() { this.tags = []; + let signatures: string[] = [] ; this.loading = true; toPromise<Tag[]>(this.tagService @@ -151,11 +153,17 @@ export class TagComponent implements OnInit { components: { total: 0, summary: [] - } - }; - } - }); - this.tags = items; + } + }; + } + if (t.signature !== null) { + signatures.push(t.name); + } + }); + this.tags = items; + let signedName: {[key: string]: string[]} = {}; + signedName[this.repoName] = signatures; + this.signatureOutput.emit(signedName); this.loading = false; if (this.tags && this.tags.length === 0) { this.refreshRepo.emit(true); diff --git a/src/ui_ng/src/i18n/lang/en-us-lang.json b/src/ui_ng/src/i18n/lang/en-us-lang.json index c49b49830d..4386ecaf54 100644 --- a/src/ui_ng/src/i18n/lang/en-us-lang.json +++ b/src/ui_ng/src/i18n/lang/en-us-lang.json @@ -321,6 +321,8 @@ "MY_REPOSITORY": "My Repository", "PUBLIC_REPOSITORY": "Public Repository", "DELETION_TITLE_REPO": "Confirm Repository Deletion", + "DELETION_TITLE_REPO_SIGNED": "Repository cannot be deleted", + "DELETION_SUMMARY_REPO_SIGNED": "Repository '{{repoName}}' cannot be deleted because the following signed images existing.\n{{signedImages}} \nYou should unsign all the signed images before deleting the repository!", "DELETION_SUMMARY_REPO": "Do you want to delete repository {{param}}?", "DELETION_TITLE_TAG": "Confirm Tag Deletion", "DELETION_SUMMARY_TAG": "Do you want to delete tag {{param}}?", diff --git a/src/ui_ng/src/i18n/lang/es-es-lang.json b/src/ui_ng/src/i18n/lang/es-es-lang.json index 9ccdd061c6..a88d5e2a12 100644 --- a/src/ui_ng/src/i18n/lang/es-es-lang.json +++ b/src/ui_ng/src/i18n/lang/es-es-lang.json @@ -322,6 +322,8 @@ "MY_REPOSITORY": "Mi Repositorio", "PUBLIC_REPOSITORY": "Repositorio Público", "DELETION_TITLE_REPO": "Confirmar Eliminación de Repositorio", + "DELETION_TITLE_REPO_SIGNED": "Repository cannot be deleted", + "DELETION_SUMMARY_REPO_SIGNED": "Repository '{{repoName}}' cannot be deleted because the following signed images existing.\n{{signedImages}} \nYou should unsign all the signed images before deleting the repository!", "DELETION_SUMMARY_REPO": "¿Quiere eliminar el repositorio {{param}}?", "DELETION_TITLE_TAG": "Confirmación de Eliminación de Etiqueta", "DELETION_SUMMARY_TAG": "¿Quiere eliminar la etiqueta {{param}}?", diff --git a/src/ui_ng/src/i18n/lang/zh-cn-lang.json b/src/ui_ng/src/i18n/lang/zh-cn-lang.json index 79f49f42ff..7bad738cdd 100644 --- a/src/ui_ng/src/i18n/lang/zh-cn-lang.json +++ b/src/ui_ng/src/i18n/lang/zh-cn-lang.json @@ -321,6 +321,8 @@ "MY_REPOSITORY": "我的仓库", "PUBLIC_REPOSITORY": "公共仓库", "DELETION_TITLE_REPO": "删除镜像仓库确认", + "DELETION_TITLE_REPO_SIGNED": "仓库不能被删除", + "DELETION_SUMMARY_REPO_SIGNED": "镜像仓库 '{{repoName}}' 不能被删除,因为存在以下签名镜像.\n{{signedImages}} \n在删除镜像仓库前需先删除所有的签名镜像", "DELETION_SUMMARY_REPO": "确认删除镜像仓库 {{param}}?", "DELETION_TITLE_TAG": "删除镜像标签确认", "DELETION_SUMMARY_TAG": "确认删除镜像标签 {{param}}?",