diff --git a/src/portal/src/app/project/repository/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html b/src/portal/src/app/project/repository/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html index ee4b3fb00..f00cb114d 100644 --- a/src/portal/src/app/project/repository/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html +++ b/src/portal/src/app/project/repository/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.html @@ -91,7 +91,7 @@ [(clrDgSelected)]="selectedRow"> @@ -110,7 +110,7 @@ (click)="addLabels()"> {{'REPOSITORY.ADD_LABELS' | translate}} - +
@@ -208,17 +208,17 @@ {{'REPOSITORY.TAGS_COUNT' | translate | uppercase}} - - {{'REPOSITORY.PUSH_TIME' | translate | uppercase}} {{'REPOSITORY.PULL_TIME' | translate | uppercase}} + + {{'REPOSITORY.PUSH_TIME' | translate | uppercase}} {{tag.name}} + {{tag.pull_time === availableTime ? '':(tag.pull_time | date: 'short')}} {{tag.push_time | date: 'short'}} - {{tag.pull_time | date: 'short'}} diff --git a/src/portal/src/app/project/repository/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts b/src/portal/src/app/project/repository/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts index 5537c5f3d..21a37ecc3 100644 --- a/src/portal/src/app/project/repository/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts +++ b/src/portal/src/app/project/repository/artifact-list-page/artifact-list/artifact-list-tab/artifact-list-tab.component.ts @@ -162,6 +162,10 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { openSelectFilterPiece = false; // could Pagination filter filters: string[]; + + scanFiinishArtifactLength: number = 0; + onScanArtifactsLength: number = 0; + constructor( private errorHandlerService: ErrorHandler, private userPermissionService: UserPermissionService, @@ -731,6 +735,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { forkJoin(...this.deleteArtifactobservableLists).subscribe((deleteResult) => { let deleteSuccessList = []; let deleteErrorList = []; + this.deleteArtifactobservableLists = []; deleteResult.forEach(result => { if (!result) { // delete success @@ -739,9 +744,9 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { deleteErrorList.push(result); } }); + this.selectedRow = []; if (deleteSuccessList.length === deleteResult.length) { // all is success - this.selectedRow = []; let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} }; this.clrLoad(st); } else if (deleteErrorList.length === deleteResult.length) { @@ -752,7 +757,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { // some artifact delete success but it has error delete things this.errorHandlerService.error(deleteErrorList[deleteErrorList.length - 1].error); // if delete one success refresh list - this.selectedRow = []; let st: ClrDatagridStateInterface = { page: {from: 0, to: this.pageSize - 1, size: this.pageSize} }; this.clrLoad(st); } @@ -871,10 +875,16 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { } // Trigger scan scanNow(): void { - if (this.selectedRow && this.selectedRow.length === 1) { - this.onSendingScanCommand = true; - this.channel.publishScanEvent(this.repoName + "/" + this.selectedRow[0].digest); + if (!this.selectedRow.length) { + return; } + this.scanFiinishArtifactLength = 0; + this.onScanArtifactsLength = this.selectedRow.length; + this.onSendingScanCommand = true; + this.selectedRow.forEach((data: any) => { + let digest = data.digest; + this.channel.publishScanEvent(this.repoName + "/" + digest); + }); } selectedRowHasVul(): boolean { return !!(this.selectedRow @@ -886,7 +896,11 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy { return !!(artifact && artifact.addition_links && artifact.addition_links[ADDITIONS.VULNERABILITIES]); } submitFinish(e: boolean) { - this.onSendingScanCommand = e; + this.scanFiinishArtifactLength += 1; + // all selected scan action has start + if (this.scanFiinishArtifactLength === this.onScanArtifactsLength) { + this.onSendingScanCommand = e; + } } // pull command onCpError($event: any): void { diff --git a/src/portal/src/app/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts b/src/portal/src/app/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts index 544db9988..2283e698c 100644 --- a/src/portal/src/app/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts +++ b/src/portal/src/app/project/repository/artifact/artifact-additions/artifact-vulnerabilities/artifact-vulnerabilities.component.ts @@ -91,7 +91,10 @@ export class ArtifactVulnerabilitiesComponent implements OnInit, OnDestroy { this.hasShowLoading = true; } this.additionsService.getDetailByLink(this.vulnerabilitiesLink.href) - .pipe(finalize(() => this.loading = false)) + .pipe(finalize(() => { + this.loading = false; + this.hasShowLoading = false; + })) .subscribe( res => { this.scan_overview = res; diff --git a/src/portal/src/app/project/repository/artifact/artifact-summary.component.html b/src/portal/src/app/project/repository/artifact/artifact-summary.component.html index aebe6d617..f8f90fedc 100644 --- a/src/portal/src/app/project/repository/artifact/artifact-summary.component.html +++ b/src/portal/src/app/project/repository/artifact/artifact-summary.component.html @@ -22,7 +22,7 @@ - diff --git a/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.html b/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.html index 319b39249..1f044e366 100644 --- a/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.html +++ b/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.html @@ -4,7 +4,7 @@ -
@@ -46,7 +46,7 @@ {{'REPOSITORY.IMMUTABLE' | translate}}
- +
diff --git a/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.spec.ts b/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.spec.ts index 2d322ef58..306b34483 100644 --- a/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.spec.ts +++ b/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.spec.ts @@ -10,6 +10,7 @@ import { ErrorHandler } from "../../../../../lib/utils/error-handler"; import { ArtifactService } from '../../../../../../ng-swagger-gen/services/artifact.service'; import { OperationService } from "../../../../../lib/components/operation/operation.service"; import { CURRENT_BASE_HREF } from "../../../../../lib/utils/utils"; +import { USERSTATICPERMISSION, UserPermissionService, UserPermissionDefaultService } from '../../../../../lib/services'; describe('ArtifactTagComponent', () => { @@ -25,6 +26,11 @@ describe('ArtifactTagComponent', () => { const config: IServiceConfig = { repositoryBaseEndpoint: CURRENT_BASE_HREF + "/repositories/testing" }; + let userPermissionService; + const permissions = [ + { resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE }, + ]; + let mockHasDeleteImagePermission: boolean = true; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ @@ -41,6 +47,7 @@ describe('ArtifactTagComponent', () => { { provide: SERVICE_CONFIG, useValue: config }, { provide: mockErrorHandler, useValue: ErrorHandler }, { provide: ArtifactService, useValue: mockArtifactService }, + { provide: UserPermissionService, useClass: UserPermissionDefaultService }, { provide: OperationService }, ] }) @@ -50,6 +57,11 @@ describe('ArtifactTagComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ArtifactTagComponent); component = fixture.componentInstance; + userPermissionService = fixture.debugElement.injector.get(UserPermissionService); + spyOn(userPermissionService, "hasProjectPermissions") + .withArgs(component.projectId, permissions) + .and.returnValue(of([ + mockHasDeleteImagePermission])); fixture.detectChanges(); }); diff --git a/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.ts b/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.ts index b036d4aab..a1f5ad539 100644 --- a/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.ts +++ b/src/portal/src/app/project/repository/artifact/artifact-tag/artifact-tag.component.ts @@ -17,7 +17,10 @@ import { errorHandler } from "../../../../../lib/utils/shared/shared.utils"; import { ArtifactFront as Artifact } from "../artifact"; import { ArtifactService } from '../../../../../../ng-swagger-gen/services/artifact.service'; import { Tag } from '../../../../../../ng-swagger-gen/models/tag'; +import { + UserPermissionService, USERSTATICPERMISSION +} from "../../../../../lib/services"; class InitTag { name = ""; } @@ -30,6 +33,7 @@ class InitTag { export class ArtifactTagComponent implements OnInit { @Input() artifactDetails: Artifact; @Input() projectName: string; + @Input() projectId: number; @Input() repositoryName: string; @Output() refreshArtifact = new EventEmitter(); newTagName = new InitTag(); @@ -43,15 +47,25 @@ export class ArtifactTagComponent implements OnInit { availableTime = AVAILABLE_TIME; @ViewChild("confirmationDialog", { static: false }) confirmationDialog: ConfirmationDialogComponent; + hasDeleteTagPermission: boolean; constructor( private operationService: OperationService, private artifactService: ArtifactService, private translateService: TranslateService, + private userPermissionService: UserPermissionService, private errorHandlerService: ErrorHandler ) { } - ngOnInit() { + this.getImagePermissionRule(this.projectId); + } + getImagePermissionRule(projectId: number): void { + const permissions = [ + { resource: USERSTATICPERMISSION.REPOSITORY_TAG.KEY, action: USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE } + ]; + this.userPermissionService.hasProjectPermissions(this.projectId, permissions).subscribe((results: Array) => { + this.hasDeleteTagPermission = results[0]; + }, error => this.errorHandlerService.error(error)); } addTag() { @@ -166,4 +180,8 @@ export class ArtifactTagComponent implements OnInit { this.openTag = !this.openTag; this.newTagformShow = false; } + hasImmutableOnTag(): boolean { + return this.selectedRow.some((artifact) => artifact.immutable); + } + } diff --git a/src/portal/src/app/shared/list-repository-ro/list-repository-ro.component.ts b/src/portal/src/app/shared/list-repository-ro/list-repository-ro.component.ts index 1a3de17ca..a3c24f029 100644 --- a/src/portal/src/app/shared/list-repository-ro/list-repository-ro.component.ts +++ b/src/portal/src/app/shared/list-repository-ro/list-repository-ro.component.ts @@ -68,8 +68,9 @@ export class ListRepositoryROComponent implements OnInit, OnDestroy { public gotoLink(projectId: number, repoName: string): void { this.searchTrigger.closeSearch(true); - - let linkUrl = ['harbor', 'tags', projectId, repoName]; + let projectName = repoName.split('/')[0]; + let repositorieName = projectName ? repoName.split(`${projectName}/`)[1] : repoName; + let linkUrl = ['harbor', 'projects', projectId, 'repositories', repositorieName ]; this.router.navigate(linkUrl); } diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 0d7568116..635068bec 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -693,7 +693,7 @@ "ADD_LABEL_TO_IMAGE": "Add labels to this image", "FILTER_BY_LABEL": "Filter images by label", "FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label", - "ADD_LABELS": "Add labels", + "ADD_LABELS": "Add Labels", "RETAG": "Copy", "ACTION": "ACTION", "DEPLOY": "DEPLOY", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 5ba36407b..d72c03634 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -694,7 +694,7 @@ "ADD_LABEL_TO_IMAGE": "Add labels to this image", "FILTER_BY_LABEL": "Filter images by label", "FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label", - "ADD_LABELS": "Add labels", + "ADD_LABELS": "Add Labels", "RETAG": "Copy", "ACTION": "ACTION", "DEPLOY": "DEPLOY", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index aa1b63a77..9898a5098 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -680,7 +680,7 @@ "ADD_LABEL_TO_IMAGE": "Add labels to this image", "FILTER_BY_LABEL": "Filter images by label", "FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label", - "ADD_LABELS": "Add labels", + "ADD_LABELS": "Add Labels", "RETAG": "Copy", "ACTION": "ACTION", "DEPLOY": "DEPLOY", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index b96703a76..61dc1250c 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -693,7 +693,7 @@ "ADD_LABEL_TO_IMAGE": "Adicionar labels a essa imagem", "FILTER_BY_LABEL": "Filtrar imagens por label", "FILTER_ARTIFACT_BY_LABEL": "Filter actifact by label", - "ADD_LABELS": "Adicionar labels", + "ADD_LABELS": "Adicionar Labels", "RETAG": "Copy", "ACTION": "AÇÃO", "DEPLOY": "DEPLOY", diff --git a/src/portal/src/lib/components/cron-schedule/cron-schedule.component.html b/src/portal/src/lib/components/cron-schedule/cron-schedule.component.html index 05a771cfd..751a29753 100644 --- a/src/portal/src/lib/components/cron-schedule/cron-schedule.component.html +++ b/src/portal/src/lib/components/cron-schedule/cron-schedule.component.html @@ -2,29 +2,33 @@ -
{{ labelEdit | translate }}
- @@ -34,27 +38,26 @@
{{ "SCHEDULE.CRON" | translate }}
-
-
-
+ \ No newline at end of file diff --git a/src/portal/src/lib/components/cron-schedule/cron-schedule.component.scss b/src/portal/src/lib/components/cron-schedule/cron-schedule.component.scss index 661ce8ec4..9bf73e162 100644 --- a/src/portal/src/lib/components/cron-schedule/cron-schedule.component.scss +++ b/src/portal/src/lib/components/cron-schedule/cron-schedule.component.scss @@ -47,6 +47,7 @@ .cron-tooltip { color: gray; cursor: default; + position: absolute; .table-box { width: 20rem; } diff --git a/tests/resources/Harbor-Pages/Project.robot b/tests/resources/Harbor-Pages/Project.robot index 62aed20a2..dbc08608f 100644 --- a/tests/resources/Harbor-Pages/Project.robot +++ b/tests/resources/Harbor-Pages/Project.robot @@ -248,7 +248,7 @@ Add Labels To Tag Retry Element Click xpath=//clr-dg-row[contains(.,'${tagName}')]//label Capture Page Screenshot add_${labelName}.png Retry Element Click xpath=//clr-dg-action-bar//clr-dropdown//span - Retry Element Click xpath=//clr-dropdown-menu//clr-dropdown//button[contains(.,'Add labels')] + Retry Element Click xpath=//clr-dropdown-menu//clr-dropdown//button[contains(.,'Add Labels')] Retry Element Click xpath=//clr-dropdown//div//label[contains(.,'${labelName}')] Retry Wait Until Page Contains Element xpath=//clr-dg-row//label[contains(.,'${labelName}')] diff --git a/tests/resources/Harbor-Pages/Vulnerability.robot b/tests/resources/Harbor-Pages/Vulnerability.robot index e966bab36..c08ca7356 100644 --- a/tests/resources/Harbor-Pages/Vulnerability.robot +++ b/tests/resources/Harbor-Pages/Vulnerability.robot @@ -76,6 +76,7 @@ Switch To Scanners Page Should Display The Default Clair Scanner Retry Wait Until Page Contains Element //clr-datagrid//clr-dg-row//clr-dg-cell[contains(.,'Clair')]//span[contains(.,'Default')] + Clair Is Immutable Scanner Retry Element Click //clr-dg-row[contains(.,'Clair')]//clr-radio-wrapper/label Retry Double Keywords When Error Retry Element Click ${scanner_action_xpath} Retry Wait Until Page Contains Element ${delete_scanner_action_xpath}