From 1a551690d3a9401f91b71d6aa86060f6678ef1a8 Mon Sep 17 00:00:00 2001 From: System Administrator Date: Wed, 9 Jan 2019 16:08:56 +0800 Subject: [PATCH] promission reset Signed-off-by: Yogi_Wang --- .../lib/src/endpoint/endpoint.component.ts | 47 +-- src/portal/lib/src/harbor-library.module.ts | 19 +- .../src/helm-chart/helm-chart.component.html | 6 +- .../src/helm-chart/helm-chart.component.ts | 87 +++--- .../helm-chart-version.component.html | 10 +- .../versions/helm-chart-version.component.ts | 26 +- src/portal/lib/src/label/label.component.html | 6 +- src/portal/lib/src/label/label.component.ts | 43 +-- .../list-replication-rule.component.html | 8 +- .../list-replication-rule.component.ts | 44 +-- .../project-policy-config.component.html | 12 +- .../project-policy-config.component.spec.ts | 5 +- .../project-policy-config.component.ts | 13 +- .../replication/replication.component.html | 9 +- .../src/replication/replication.component.ts | 4 + .../repository-gridview.component.html | 4 +- .../repository-gridview.component.spec.ts | 19 +- .../repository-gridview.component.ts | 111 ++++--- .../src/repository/repository.component.html | 2 +- .../repository/repository.component.spec.ts | 2 + src/portal/lib/src/service/index.ts | 2 + src/portal/lib/src/service/interface.ts | 29 +- .../lib/src/service/permission-static.ts | 134 +++++++++ .../lib/src/service/permission.service.ts | 76 +++++ src/portal/lib/src/shared/shared.const.ts | 4 +- .../lib/src/tag/tag-detail.component.html | 6 +- .../lib/src/tag/tag-detail.component.spec.ts | 18 +- .../lib/src/tag/tag-detail.component.ts | 27 +- src/portal/lib/src/tag/tag.component.html | 6 +- src/portal/lib/src/tag/tag.component.spec.ts | 29 +- src/portal/lib/src/tag/tag.component.ts | 280 +++++++++--------- src/portal/lib/src/utils.ts | 8 + .../result-grid.component.html | 2 +- .../result-grid.component.spec.ts | 4 +- .../result-grid.component.ts | 22 +- .../result-tip.component.spec.ts | 4 +- src/portal/package-lock.json | 60 ++-- src/portal/package.json | 6 +- .../src/app/config/config.component.html | 5 +- .../list-chart-versions.component.html | 2 - .../list-chart-versions.component.ts | 4 - .../list-charts/list-charts.component.html | 1 - .../list-charts/list-charts.component.ts | 2 - .../add-member/add-member.component.html | 4 + .../app/project/member/member.component.html | 13 +- .../app/project/member/member.component.ts | 85 +++--- .../project-config.component.html | 2 +- .../project-config.component.ts | 2 - .../project-detail.component.html | 16 +- .../project-detail.component.ts | 68 ++++- .../project-label.component.html | 9 +- .../project-label/project-label.component.ts | 29 +- .../robot-account.component.html | 31 +- .../robot-account/robot-account.component.ts | 28 +- .../replication-page.component.html | 11 +- .../replication/replication-page.component.ts | 63 ++-- .../total-replication-page.component.html | 11 +- .../tag-detail/tag-detail-page.component.html | 10 +- .../tag-detail/tag-detail-page.component.ts | 4 - .../message-handler.service.ts | 4 +- .../route/member-guard-activate.service.ts | 2 +- .../route/sign-in-guard-activate.service.ts | 5 +- src/portal/src/app/shared/session.service.ts | 10 +- src/portal/src/app/shared/shared.const.ts | 9 +- src/portal/src/app/user/user.component.ts | 36 +-- src/portal/src/i18n/lang/en-us-lang.json | 1 + src/portal/src/i18n/lang/es-es-lang.json | 1 + src/portal/src/i18n/lang/fr-fr-lang.json | 1 + src/portal/src/i18n/lang/pt-br-lang.json | 1 + src/portal/src/i18n/lang/zh-cn-lang.json | 1 + .../Harbor-Pages/Project-Config.robot | 1 + tests/resources/Harbor-Pages/Project.robot | 11 +- .../Harbor-Pages/ToolKit_Elements.robot | 4 +- .../Harbor-Pages/Vulnerability.robot | 1 + 74 files changed, 1128 insertions(+), 554 deletions(-) create mode 100644 src/portal/lib/src/service/permission-static.ts create mode 100644 src/portal/lib/src/service/permission.service.ts diff --git a/src/portal/lib/src/endpoint/endpoint.component.ts b/src/portal/lib/src/endpoint/endpoint.component.ts index ad5213810..fd77076ff 100644 --- a/src/portal/lib/src/endpoint/endpoint.component.ts +++ b/src/portal/lib/src/endpoint/endpoint.component.ts @@ -19,8 +19,8 @@ import { ChangeDetectionStrategy, ChangeDetectorRef } from "@angular/core"; -import { Subscription} from "rxjs"; -import {forkJoin} from "rxjs"; +import { Subscription } from "rxjs"; +import { forkJoin } from "rxjs"; import { TranslateService } from "@ngx-translate/core"; import { Comparator } from "../service/interface"; @@ -29,9 +29,9 @@ import { EndpointService } from "../service/endpoint.service"; import { ErrorHandler } from "../error-handler/index"; -import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message"; -import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message"; -import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component"; +import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message"; +import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message"; +import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component"; import { ConfirmationTargets, @@ -42,8 +42,9 @@ import { import { CreateEditEndpointComponent } from "../create-edit-endpoint/create-edit-endpoint.component"; import { toPromise, CustomComparator } from "../utils"; -import {operateChanges, OperateInfo, OperationState} from "../operation/operate"; -import {OperationService} from "../operation/operation.service"; +import { operateChanges, OperateInfo, OperationState } from "../operation/operate"; +import { OperationService } from "../operation/operation.service"; + @Component({ selector: "hbr-endpoint", @@ -86,10 +87,10 @@ export class EndpointComponent implements OnInit, OnDestroy { } constructor(private endpointService: EndpointService, - private errorHandler: ErrorHandler, - private translateService: TranslateService, - private operationService: OperationService, - private ref: ChangeDetectorRef) { + private errorHandler: ErrorHandler, + private translateService: TranslateService, + private operationService: OperationService, + private ref: ChangeDetectorRef) { this.forceRefreshView(1000); } @@ -208,18 +209,18 @@ export class EndpointComponent implements OnInit, OnDestroy { operateChanges(operMessage, OperationState.success); }); }).catch( - error => { - if (error && error.status === 412) { - forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), - this.translateService.get('DESTINATION.FAILED_TO_DELETE_TARGET_IN_USED')).subscribe(res => { - operateChanges(operMessage, OperationState.failure, res[1]); - }); - } else { - this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => { - operateChanges(operMessage, OperationState.failure, res); - }); - } - }); + error => { + if (error && error.status === 412) { + forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), + this.translateService.get('DESTINATION.FAILED_TO_DELETE_TARGET_IN_USED')).subscribe(res => { + operateChanges(operMessage, OperationState.failure, res[1]); + }); + } else { + this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => { + operateChanges(operMessage, OperationState.failure, res); + }); + } + }); } // Forcely refresh the view diff --git a/src/portal/lib/src/harbor-library.module.ts b/src/portal/lib/src/harbor-library.module.ts index 2c014e762..2c7370f89 100644 --- a/src/portal/lib/src/harbor-library.module.ts +++ b/src/portal/lib/src/harbor-library.module.ts @@ -29,7 +29,6 @@ import { CREATE_EDIT_LABEL_DIRECTIVES } from "./create-edit-label/index"; import { LABEL_PIECE_DIRECTIVES } from "./label-piece/index"; import { HELMCHART_DIRECTIVE } from "./helm-chart/index"; import { IMAGE_NAME_INPUT_DIRECTIVES } from "./image-name-input/index"; - import { SystemInfoService, SystemInfoDefaultService, @@ -56,7 +55,9 @@ import { HelmChartService, HelmChartDefaultService, RetagService, - RetagDefaultService + RetagDefaultService, + UserPermissionService, + UserPermissionDefaultService } from './service/index'; import { ErrorHandler, @@ -68,7 +69,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { TranslateServiceInitializer } from './i18n/index'; import { DEFAULT_LANG_COOKIE_KEY, DEFAULT_SUPPORTING_LANGS, DEFAULT_LANG } from './utils'; import { ChannelService } from './channel/index'; -import { OperationService } from './operation/operation.service'; +import { OperationService } from './operation/operation.service'; /** * Declare default service configuration; all the endpoints will be defined in @@ -151,6 +152,8 @@ export interface HarborModuleConfig { // Service implementation for helmchart helmChartService?: Provider; + // Service implementation for userPermission + userPermissionService?: Provider; } /** @@ -248,8 +251,9 @@ export class HarborLibraryModule { config.configService || { provide: ConfigurationService, useClass: ConfigurationDefaultService }, config.jobLogService || { provide: JobLogService, useClass: JobLogDefaultService }, config.projectPolicyService || { provide: ProjectService, useClass: ProjectDefaultService }, - config.labelService || {provide: LabelService, useClass: LabelDefaultService}, - config.helmChartService || {provide: HelmChartService, useClass: HelmChartDefaultService}, + config.labelService || { provide: LabelService, useClass: LabelDefaultService }, + config.helmChartService || { provide: HelmChartService, useClass: HelmChartDefaultService }, + config.userPermissionService || { provide: UserPermissionService, useClass: UserPermissionDefaultService }, // Do initializing TranslateServiceInitializer, { @@ -281,8 +285,9 @@ export class HarborLibraryModule { config.configService || { provide: ConfigurationService, useClass: ConfigurationDefaultService }, config.jobLogService || { provide: JobLogService, useClass: JobLogDefaultService }, config.projectPolicyService || { provide: ProjectService, useClass: ProjectDefaultService }, - config.labelService || {provide: LabelService, useClass: LabelDefaultService}, - config.helmChartService || {provide: HelmChartService, useClass: HelmChartDefaultService}, + config.labelService || { provide: LabelService, useClass: LabelDefaultService }, + config.helmChartService || { provide: HelmChartService, useClass: HelmChartDefaultService }, + config.userPermissionService || { provide: UserPermissionService, useClass: UserPermissionDefaultService }, ChannelService, OperationService ] diff --git a/src/portal/lib/src/helm-chart/helm-chart.component.html b/src/portal/lib/src/helm-chart/helm-chart.component.html index bade63a46..a9d1b75c1 100644 --- a/src/portal/lib/src/helm-chart/helm-chart.component.html +++ b/src/portal/lib/src/helm-chart/helm-chart.component.html @@ -23,14 +23,14 @@
- - - diff --git a/src/portal/lib/src/helm-chart/helm-chart.component.ts b/src/portal/lib/src/helm-chart/helm-chart.component.ts index f3d6832ec..792eb9333 100644 --- a/src/portal/lib/src/helm-chart/helm-chart.component.ts +++ b/src/portal/lib/src/helm-chart/helm-chart.component.ts @@ -17,9 +17,11 @@ import { SystemInfo, SystemInfoService, HelmChartItem } from "../service/index"; import { ErrorHandler } from "../error-handler/error-handler"; import { toPromise, DEFAULT_PAGE_SIZE, downloadFile } from "../utils"; import { HelmChartService } from "../service/helm-chart.service"; -import { DefaultHelmIcon} from "../shared/shared.const"; +import { DefaultHelmIcon } from "../shared/shared.const"; import { Roles } from './../shared/shared.const'; import { OperationService } from "./../operation/operation.service"; +import { UserPermissionService } from "../service/permission.service"; +import { USERSTATICPERMISSION } from "../service/permission-static"; import { OperateInfo, OperationState, @@ -45,7 +47,6 @@ export class HelmChartComponent implements OnInit { @Input() urlPrefix: string; @Input() hasSignedIn: boolean; @Input() projectRoleID = Roles.OTHER; - @Input() hasProjectAdminRole: boolean; @Output() chartClickEvt = new EventEmitter(); @Output() chartDownloadEve = new EventEmitter(); @Input() chartDefaultIcon: string = DefaultHelmIcon; @@ -76,24 +77,23 @@ export class HelmChartComponent implements OnInit { @ViewChild('chartUploadForm') uploadForm: NgForm; @ViewChild("confirmationDialog") confirmationDialog: ConfirmationDialogComponent; - + hasUploadHelmChartsPermission: boolean; + hasDownloadHelmChartsPermission: boolean; + hasDeleteHelmChartsPermission: boolean; constructor( private errorHandler: ErrorHandler, private translateService: TranslateService, private systemInfoService: SystemInfoService, private helmChartService: HelmChartService, + private userPermissionService: UserPermissionService, private operationService: OperationService, private cdr: ChangeDetectorRef, - ) {} + ) { } public get registryUrl(): string { return this.systemInfo ? this.systemInfo.registry_url : ""; } - public get developerRoleOrAbove(): boolean { - return this.projectRoleID === Roles.DEVELOPER || this.hasProjectAdminRole; - } - ngOnInit(): void { // Get system info for tag views toPromise(this.systemInfoService.getSystemInfo()) @@ -101,8 +101,21 @@ export class HelmChartComponent implements OnInit { .catch(error => this.errorHandler.error(error)); this.lastFilteredChartName = ""; this.refresh(); + this.getHelmPermissionRule(this.projectId); + } + getHelmPermissionRule(projectId: number): void { + let hasUploadHelmChartsPermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.HELM_CHART.KEY, USERSTATICPERMISSION.HELM_CHART.VALUE.UPLOAD); + let hasDownloadHelmChartsPermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.HELM_CHART.KEY, USERSTATICPERMISSION.HELM_CHART.VALUE.DOWNLOAD); + let hasDeleteHelmChartsPermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.HELM_CHART.KEY, USERSTATICPERMISSION.HELM_CHART.VALUE.DELETE); + forkJoin(hasUploadHelmChartsPermission, hasDownloadHelmChartsPermission, hasDeleteHelmChartsPermission).subscribe(permissions => { + this.hasUploadHelmChartsPermission = permissions[0] as boolean; + this.hasDownloadHelmChartsPermission = permissions[1] as boolean; + this.hasDeleteHelmChartsPermission = permissions[2] as boolean; + }, error => this.errorHandler.error(error)); } - updateFilterValue(value: string) { this.lastFilteredChartName = value; this.refresh(); @@ -111,22 +124,22 @@ export class HelmChartComponent implements OnInit { refresh() { this.loading = true; this.helmChartService - .getHelmCharts(this.projectName) - .pipe(finalize(() => { + .getHelmCharts(this.projectName) + .pipe(finalize(() => { let hnd = setInterval(() => this.cdr.markForCheck(), 100); setTimeout(() => clearInterval(hnd), 3000); this.loading = false; - })) - .subscribe( - charts => { - this.charts = charts.filter(x => x.name.includes(this.lastFilteredChartName)); - this.chartsCopy = charts.map(x => Object.assign({}, x)); - this.totalCount = charts.length; - }, - err => { - this.errorHandler.error(err); - } - ); + })) + .subscribe( + charts => { + this.charts = charts.filter(x => x.name.includes(this.lastFilteredChartName)); + this.chartsCopy = charts.map(x => Object.assign({}, x)); + this.totalCount = charts.length; + }, + err => { + this.errorHandler.error(err); + } + ); } onChartClick(item: HelmChartItem) { @@ -163,10 +176,10 @@ export class HelmChartComponent implements OnInit { this.refresh(); })) .subscribe(() => { - this.translateService - .get("HELM_CHART.FILE_UPLOADED") - .subscribe(res => this.errorHandler.info(res)); - }, + this.translateService + .get("HELM_CHART.FILE_UPLOADED") + .subscribe(res => this.errorHandler.info(res)); + }, err => this.errorHandler.error(err) ); } @@ -192,23 +205,23 @@ export class HelmChartComponent implements OnInit { this.operationService.publishInfo(operateMsg); return this.helmChartService.deleteHelmChart(this.projectName, chartName) - .pipe(map( - () => operateChanges(operateMsg, OperationState.success), - err => operateChanges(operateMsg, OperationState.failure, err) - )); + .pipe(map( + () => operateChanges(operateMsg, OperationState.success), + err => operateChanges(operateMsg, OperationState.failure, err) + )); } deleteCharts(charts: HelmChartItem[]) { if (charts && charts.length < 1) { return; } let chartsDelete$ = charts.map(chart => this.deleteChart(chart.name)); forkJoin(chartsDelete$) - .pipe( - catchError(err => throwError(err)), - finalize(() => { - this.refresh(); - this.selectedRows = []; - })) - .subscribe(() => {}); + .pipe( + catchError(err => throwError(err)), + finalize(() => { + this.refresh(); + this.selectedRows = []; + })) + .subscribe(() => { }); } downloadLatestVersion(evt?: Event, item?: HelmChartItem) { diff --git a/src/portal/lib/src/helm-chart/versions/helm-chart-version.component.html b/src/portal/lib/src/helm-chart/versions/helm-chart-version.component.html index 1cd96ab41..929cdc64e 100644 --- a/src/portal/lib/src/helm-chart/versions/helm-chart-version.component.html +++ b/src/portal/lib/src/helm-chart/versions/helm-chart-version.component.html @@ -38,18 +38,18 @@ - @@ -144,7 +144,7 @@
diff --git a/src/portal/lib/src/helm-chart/versions/helm-chart-version.component.ts b/src/portal/lib/src/helm-chart/versions/helm-chart-version.component.ts index 276f5dbd6..c92e724ca 100644 --- a/src/portal/lib/src/helm-chart/versions/helm-chart-version.component.ts +++ b/src/portal/lib/src/helm-chart/versions/helm-chart-version.component.ts @@ -26,6 +26,8 @@ import { ErrorHandler } from "./../../error-handler/error-handler"; import { toPromise, DEFAULT_PAGE_SIZE, downloadFile } from "../../utils"; import { OperationService } from "./../../operation/operation.service"; import { HelmChartService } from "./../../service/helm-chart.service"; +import { UserPermissionService } from "../../service/permission.service"; +import { USERSTATICPERMISSION } from "../../service/permission-static"; import { ConfirmationAcknowledgement, ConfirmationDialogComponent, ConfirmationMessage } from "./../../confirmation-dialog"; import { OperateInfo, @@ -49,13 +51,11 @@ import { }) export class ChartVersionComponent implements OnInit { signedCon: { [key: string]: any | string[] } = {}; - @Input() projectRoleID: number; @Input() projectId: number; @Input() projectName: string; @Input() chartName: string; @Input() roleName: string; @Input() hasSignedIn: boolean; - @Input() hasProjectAdminRole: boolean; @Input() chartDefaultIcon: string = DefaultHelmIcon; @Output() versionClickEvt = new EventEmitter(); @Output() backEvt = new EventEmitter(); @@ -85,12 +85,15 @@ export class ChartVersionComponent implements OnInit { @ViewChild("confirmationDialog") confirmationDialog: ConfirmationDialogComponent; - + hasAddRemoveHelmChartVersionPermission: boolean; + hasDownloadHelmChartVersionPermission: boolean; + hasDeleteHelmChartVersionPermission: boolean; constructor( private errorHandler: ErrorHandler, private systemInfoService: SystemInfoService, private helmChartService: HelmChartService, private resrouceLabelService: LabelService, + public userPermissionService: UserPermissionService, private cdr: ChangeDetectorRef, private operationService: OperationService, ) { } @@ -107,6 +110,7 @@ export class ChartVersionComponent implements OnInit { this.refresh(); this.getLabels(); this.lastFilteredVersionName = ""; + this.getHelmChartVersionPermission(this.projectId); } updateFilterValue(value: string) { @@ -326,7 +330,19 @@ export class ChartVersionComponent implements OnInit { }); } - public get developerRoleOrAbove(): boolean { - return this.projectRoleID === Roles.DEVELOPER || this.hasProjectAdminRole; + getHelmChartVersionPermission(projectId: number): void { + + let hasAddRemoveHelmChartVersionPermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.HELM_CHART_VERSION_LABEL.KEY, USERSTATICPERMISSION.HELM_CHART_VERSION_LABEL.VALUE.CREATE); + let hasDownloadHelmChartVersionPermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.HELM_CHART_VERSION.KEY, USERSTATICPERMISSION.HELM_CHART_VERSION.VALUE.READ); + let hasDeleteHelmChartVersionPermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.HELM_CHART_VERSION.KEY, USERSTATICPERMISSION.HELM_CHART_VERSION.VALUE.DELETE); + forkJoin(hasAddRemoveHelmChartVersionPermission, hasDownloadHelmChartVersionPermission, hasDeleteHelmChartVersionPermission) + .subscribe(permissions => { + this.hasAddRemoveHelmChartVersionPermission = permissions[0] as boolean; + this.hasDownloadHelmChartVersionPermission = permissions[1] as boolean; + this.hasDeleteHelmChartVersionPermission = permissions[2] as boolean; + }, error => this.errorHandler.error(error)); } } diff --git a/src/portal/lib/src/label/label.component.html b/src/portal/lib/src/label/label.component.html index 7749f6bb2..717f9faf9 100644 --- a/src/portal/lib/src/label/label.component.html +++ b/src/portal/lib/src/label/label.component.html @@ -11,9 +11,9 @@
- - - + + +
diff --git a/src/portal/lib/src/label/label.component.ts b/src/portal/lib/src/label/label.component.ts index 7a3f3c374..1867440d6 100644 --- a/src/portal/lib/src/label/label.component.ts +++ b/src/portal/lib/src/label/label.component.ts @@ -19,22 +19,22 @@ import { ChangeDetectorRef, Input } from "@angular/core"; -import {Label} from "../service/interface"; -import {LabelService} from "../service/label.service"; -import {toPromise} from "../utils"; -import {ErrorHandler} from "../error-handler/error-handler"; -import {CreateEditLabelComponent} from "../create-edit-label/create-edit-label.component"; -import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message"; +import { Label } from "../service/interface"; +import { LabelService } from "../service/label.service"; +import { toPromise } from "../utils"; +import { ErrorHandler } from "../error-handler/error-handler"; +import { CreateEditLabelComponent } from "../create-edit-label/create-edit-label.component"; +import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message"; import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../shared/shared.const"; -import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message"; -import {TranslateService} from "@ngx-translate/core"; -import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component"; -import {operateChanges, OperateInfo, OperationState} from "../operation/operate"; -import {OperationService} from "../operation/operation.service"; +import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message"; +import { TranslateService } from "@ngx-translate/core"; +import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component"; +import { operateChanges, OperateInfo, OperationState } from "../operation/operate"; +import { OperationService } from "../operation/operation.service"; @Component({ selector: "hbr-label", @@ -51,7 +51,9 @@ export class LabelComponent implements OnInit { @Input() scope: string; @Input() projectId = 0; - @Input() hasProjectAdminRole: boolean; + @Input() hasCreateLabelPermission: boolean; + @Input() hasUpdateLabelPermission: boolean; + @Input() hasDeleteLabelPermission: boolean; @ViewChild(CreateEditLabelComponent) createEditLabel: CreateEditLabelComponent; @@ -59,10 +61,10 @@ export class LabelComponent implements OnInit { confirmationDialogComponent: ConfirmationDialogComponent; constructor(private labelService: LabelService, - private errorHandler: ErrorHandler, - private translateService: TranslateService, - private operationService: OperationService, - private ref: ChangeDetectorRef) { + private errorHandler: ErrorHandler, + private translateService: TranslateService, + private operationService: OperationService, + private ref: ChangeDetectorRef) { } ngOnInit(): void { @@ -162,11 +164,11 @@ export class LabelComponent implements OnInit { operateChanges(operMessage, OperationState.success); }); }).catch( - error => { - this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => { - operateChanges(operMessage, OperationState.failure, res); + error => { + this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => { + operateChanges(operMessage, OperationState.failure, res); + }); }); - }); } // Forcely refresh the view @@ -183,4 +185,5 @@ export class LabelComponent implements OnInit { } }, duration); } + } diff --git a/src/portal/lib/src/list-replication-rule/list-replication-rule.component.html b/src/portal/lib/src/list-replication-rule/list-replication-rule.component.html index 88d5a28cd..83188bda5 100644 --- a/src/portal/lib/src/list-replication-rule/list-replication-rule.component.html +++ b/src/portal/lib/src/list-replication-rule/list-replication-rule.component.html @@ -1,10 +1,10 @@
- - - - + + + + {{'REPLICATION.NAME' | translate}} {{'REPLICATION.STATUS' | translate}} diff --git a/src/portal/lib/src/list-replication-rule/list-replication-rule.component.ts b/src/portal/lib/src/list-replication-rule/list-replication-rule.component.ts index 4107dc194..f593ff25a 100644 --- a/src/portal/lib/src/list-replication-rule/list-replication-rule.component.ts +++ b/src/portal/lib/src/list-replication-rule/list-replication-rule.component.ts @@ -24,28 +24,29 @@ import { SimpleChange, SimpleChanges } from "@angular/core"; -import { forkJoin} from "rxjs"; +import { forkJoin } from "rxjs"; import { Comparator } from "../service/interface"; import { TranslateService } from "@ngx-translate/core"; -import {ReplicationService} from "../service/replication.service"; +import { ReplicationService } from "../service/replication.service"; + import { ReplicationJob, ReplicationJobItem, ReplicationRule } from "../service/interface"; -import {ConfirmationDialogComponent} from "../confirmation-dialog/confirmation-dialog.component"; -import {ConfirmationMessage} from "../confirmation-dialog/confirmation-message"; -import {ConfirmationAcknowledgement} from "../confirmation-dialog/confirmation-state-message"; +import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component"; +import { ConfirmationMessage } from "../confirmation-dialog/confirmation-message"; +import { ConfirmationAcknowledgement } from "../confirmation-dialog/confirmation-state-message"; import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from "../shared/shared.const"; -import {ErrorHandler} from "../error-handler/error-handler"; -import {toPromise, CustomComparator} from "../utils"; -import {operateChanges, OperateInfo, OperationState} from "../operation/operate"; -import {OperationService} from "../operation/operation.service"; +import { ErrorHandler } from "../error-handler/error-handler"; +import { toPromise, CustomComparator } from "../utils"; +import { operateChanges, OperateInfo, OperationState } from "../operation/operate"; +import { OperationService } from "../operation/operation.service"; @Component({ @@ -58,12 +59,14 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { nullTime = "0001-01-01T00:00:00Z"; @Input() projectId: number; - @Input() isSystemAdmin: boolean; @Input() selectedId: number | string; @Input() withReplicationJob: boolean; @Input() loading = false; - + @Input() hasCreateReplicationPermission: boolean; + @Input() hasUpdateReplicationPermission: boolean; + @Input() hasDeleteReplicationPermission: boolean; + @Input() hasExecuteReplicationPermission: boolean; @Output() reload = new EventEmitter(); @Output() selectOne = new EventEmitter(); @Output() editOne = new EventEmitter(); @@ -92,10 +95,10 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { enabledComparator: Comparator = new CustomComparator("enabled", "number"); constructor(private replicationService: ReplicationService, - private translateService: TranslateService, - private errorHandler: ErrorHandler, - private operationService: OperationService, - private ref: ChangeDetectorRef) { + private translateService: TranslateService, + private errorHandler: ErrorHandler, + private operationService: OperationService, + private ref: ChangeDetectorRef) { setInterval(() => ref.markForCheck(), 500); } @@ -113,7 +116,6 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { this.retrieveRules(); } } - ngOnChanges(changes: SimpleChanges): void { let proIdChange: SimpleChange = changes["projectId"]; if (proIdChange) { @@ -156,7 +158,7 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { let count = 0; rule.filters.forEach((data: any) => { if (data.kind === 'label' && data.value.deleted) { - count ++; + count++; } }); if (count === 0) { @@ -258,8 +260,8 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { if (!this.canDeleteRule) { forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), this.translateService.get('REPLICATION.DELETION_SUMMARY_FAILURE')).subscribe(res => { - operateChanges(operMessage, OperationState.failure, res[1]); - }); + operateChanges(operMessage, OperationState.failure, res[1]); + }); return null; } @@ -273,8 +275,8 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges { if (error && error.status === 412) { forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), this.translateService.get('REPLICATION.FAILED_TO_DELETE_POLICY_ENABLED')).subscribe(res => { - operateChanges(operMessage, OperationState.failure, res[1]); - }); + operateChanges(operMessage, OperationState.failure, res[1]); + }); } else { this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => { operateChanges(operMessage, OperationState.failure, res); diff --git a/src/portal/lib/src/project-policy-config/project-policy-config.component.html b/src/portal/lib/src/project-policy-config/project-policy-config.component.html index c9aa2458f..17263599f 100644 --- a/src/portal/lib/src/project-policy-config/project-policy-config.component.html +++ b/src/portal/lib/src/project-policy-config/project-policy-config.component.html @@ -5,7 +5,7 @@
+ [disabled]="!hasChangeConfigRole" /> @@ -19,7 +19,7 @@
- + @@ -28,7 +28,7 @@
+ [disabled]="!hasChangeConfigRole" /> @@ -52,16 +52,16 @@
-
- - diff --git a/src/portal/lib/src/project-policy-config/project-policy-config.component.spec.ts b/src/portal/lib/src/project-policy-config/project-policy-config.component.spec.ts index 640fb6913..4983c9f13 100644 --- a/src/portal/lib/src/project-policy-config/project-policy-config.component.spec.ts +++ b/src/portal/lib/src/project-policy-config/project-policy-config.component.spec.ts @@ -8,7 +8,7 @@ import { ProjectService, ProjectDefaultService} from '../service/project.service import { SERVICE_CONFIG, IServiceConfig} from '../service.config'; import { SystemInfo } from '../service/interface'; import { Project } from './project'; - +import { UserPermissionService, UserPermissionDefaultService } from '../service/permission.service'; describe('ProjectPolicyConfigComponent', () => { let systemInfoService: SystemInfoService; @@ -102,7 +102,8 @@ describe('ProjectPolicyConfigComponent', () => { ErrorHandler, { provide: SERVICE_CONFIG, useValue: config }, { provide: ProjectService, useClass: ProjectDefaultService }, - { provide: SystemInfoService, useClass: SystemInfoDefaultService} + { provide: SystemInfoService, useClass: SystemInfoDefaultService}, + { provide: UserPermissionService, useClass: UserPermissionDefaultService}, ] }) .compileComponents(); diff --git a/src/portal/lib/src/project-policy-config/project-policy-config.component.ts b/src/portal/lib/src/project-policy-config/project-policy-config.component.ts index e97f4429b..650e9b5d3 100644 --- a/src/portal/lib/src/project-policy-config/project-policy-config.component.ts +++ b/src/portal/lib/src/project-policy-config/project-policy-config.component.ts @@ -13,6 +13,8 @@ import { TranslateService } from '@ngx-translate/core'; import { Project } from './project'; import {SystemInfo, SystemInfoService} from '../service/index'; +import { UserPermissionService } from '../service/permission.service'; +import { USERSTATICPERMISSION } from '../service/permission-static'; export class ProjectPolicy { Public: boolean; @@ -56,7 +58,7 @@ export class ProjectPolicyConfigComponent implements OnInit { systemInfo: SystemInfo; orgProjectPolicy = new ProjectPolicy(); projectPolicy = new ProjectPolicy(); - + hasChangeConfigRole: boolean; severityOptions = [ {severity: 'high', severityLevel: 'VULNERABILITY.SEVERITY.HIGH'}, {severity: 'medium', severityLevel: 'VULNERABILITY.SEVERITY.MEDIUM'}, @@ -69,6 +71,7 @@ export class ProjectPolicyConfigComponent implements OnInit { private translate: TranslateService, private projectService: ProjectService, private systemInfoService: SystemInfoService, + private userPermission: UserPermissionService ) {} ngOnInit(): void { @@ -85,8 +88,14 @@ export class ProjectPolicyConfigComponent implements OnInit { // retrive project level policy data this.retrieve(); + this.getPermission(); + } + private getPermission(): void { + this.userPermission.getPermission(this.projectId, + USERSTATICPERMISSION.CONFIGURATION.KEY, USERSTATICPERMISSION.CONFIGURATION.VALUE.UPDATE).subscribe(permissins => { + this.hasChangeConfigRole = permissins as boolean; + }); } - public get withNotary(): boolean { return this.systemInfo ? this.systemInfo.with_notary : false; } diff --git a/src/portal/lib/src/replication/replication.component.html b/src/portal/lib/src/replication/replication.component.html index 512d0b864..f4d9a2a6c 100644 --- a/src/portal/lib/src/replication/replication.component.html +++ b/src/portal/lib/src/replication/replication.component.html @@ -11,9 +11,14 @@
- + (reload)="reloadRules($event)" [loading]="loading" [withReplicationJob]="withReplicationJob" (redirect)="customRedirect($event)" + [hasCreateReplicationPermission]="hasCreateReplicationPermission" + [hasUpdateReplicationPermission]="hasUpdateReplicationPermission" + [hasDeleteReplicationPermission]="hasDeleteReplicationPermission" + [hasExecuteReplicationPermission]="hasExecuteReplicationPermission" + >
diff --git a/src/portal/lib/src/replication/replication.component.ts b/src/portal/lib/src/replication/replication.component.ts index d16bc80c3..ece3c95da 100644 --- a/src/portal/lib/src/replication/replication.component.ts +++ b/src/portal/lib/src/replication/replication.component.ts @@ -104,6 +104,10 @@ export class ReplicationComponent implements OnInit, OnDestroy { @Input() isSystemAdmin: boolean; @Input() withAdmiral: boolean; @Input() withReplicationJob: boolean; + @Input() hasCreateReplicationPermission: boolean; + @Input() hasUpdateReplicationPermission: boolean; + @Input() hasDeleteReplicationPermission: boolean; + @Input() hasExecuteReplicationPermission: boolean; @Output() redirect = new EventEmitter(); @Output() openCreateRule = new EventEmitter(); diff --git a/src/portal/lib/src/repository-gridview/repository-gridview.component.html b/src/portal/lib/src/repository-gridview/repository-gridview.component.html index 6fd137c12..fc1fecde0 100644 --- a/src/portal/lib/src/repository-gridview/repository-gridview.component.html +++ b/src/portal/lib/src/repository-gridview/repository-gridview.component.html @@ -7,7 +7,7 @@ {{'CONFIG.REGISTRY_CERTIFICATE' | translate | uppercase}} - + @@ -27,7 +27,7 @@ - + {{'REPOSITORY.NAME' | translate}} {{'REPOSITORY.TAGS_COUNT' | translate}} diff --git a/src/portal/lib/src/repository-gridview/repository-gridview.component.spec.ts b/src/portal/lib/src/repository-gridview/repository-gridview.component.spec.ts index e1fa5c128..8f0d06858 100644 --- a/src/portal/lib/src/repository-gridview/repository-gridview.component.spec.ts +++ b/src/portal/lib/src/repository-gridview/repository-gridview.component.spec.ts @@ -24,13 +24,16 @@ import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index'; import { LabelPieceComponent } from "../label-piece/label-piece.component"; import { OperationService } from "../operation/operation.service"; import {ProjectDefaultService, ProjectService, RetagDefaultService, RetagService} from "../service"; - +import { UserPermissionService, UserPermissionDefaultService } from "../service/permission.service"; +import { USERSTATICPERMISSION } from "../service/permission-static"; +import { of } from "rxjs"; describe('RepositoryComponentGridview (inline template)', () => { let compRepo: RepositoryGridviewComponent; let fixtureRepo: ComponentFixture; let repositoryService: RepositoryService; let systemInfoService: SystemInfoService; + let userPermissionService: UserPermissionService; let spyRepos: jasmine.Spy; let spySystemInfo: jasmine.Spy; @@ -72,7 +75,8 @@ describe('RepositoryComponentGridview (inline template)', () => { metadata: {xTotalCount: 2}, data: mockRepoData }; - + let mockHasCreateRepositoryPermission: boolean = true; + let mockHasDeleteRepositoryPermission: boolean = true; // let mockTagData: Tag[] = [ // { // "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", @@ -120,6 +124,7 @@ describe('RepositoryComponentGridview (inline template)', () => { { provide: ProjectService, useClass: ProjectDefaultService }, { provide: RetagService, useClass: RetagDefaultService }, { provide: SystemInfoService, useClass: SystemInfoDefaultService }, + { provide: UserPermissionService, useClass: UserPermissionDefaultService }, { provide: OperationService } ] }); @@ -136,9 +141,17 @@ describe('RepositoryComponentGridview (inline template)', () => { spyRepos = spyOn(repositoryService, 'getRepositories').and.returnValues(Promise.resolve(mockRepo)); spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValues(Promise.resolve(mockSystemInfo)); + + + userPermissionService = fixtureRepo.debugElement.injector.get(UserPermissionService); + spyOn(userPermissionService, "getPermission") + .withArgs(compRepo.projectId, + USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.CREATE ) + .and.returnValue(of(mockHasCreateRepositoryPermission)) + .withArgs(compRepo.projectId, USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.DELETE ) + .and.returnValue(of(mockHasDeleteRepositoryPermission)); fixtureRepo.detectChanges(); }); - it('should create', () => { expect(compRepo).toBeTruthy(); }); diff --git a/src/portal/lib/src/repository-gridview/repository-gridview.component.ts b/src/portal/lib/src/repository-gridview/repository-gridview.component.ts index 904d6c8c1..baf7f9fce 100644 --- a/src/portal/lib/src/repository-gridview/repository-gridview.component.ts +++ b/src/portal/lib/src/repository-gridview/repository-gridview.component.ts @@ -14,8 +14,8 @@ import { import { Router } from "@angular/router"; import { forkJoin } from "rxjs"; import { finalize } from "rxjs/operators"; -import {TranslateService} from "@ngx-translate/core"; -import {Comparator, State} from "../service/interface"; +import { TranslateService } from "@ngx-translate/core"; +import { Comparator, State } from "../service/interface"; import { Repository, @@ -26,17 +26,19 @@ import { RepositoryItem, TagService } from '../service/index'; -import {ErrorHandler} from '../error-handler/error-handler'; -import {toPromise, CustomComparator, DEFAULT_PAGE_SIZE, calculatePage, doFiltering, doSorting, clone} from '../utils'; -import {ConfirmationState, ConfirmationTargets, ConfirmationButtons} from '../shared/shared.const'; -import {ConfirmationDialogComponent} from '../confirmation-dialog/confirmation-dialog.component'; -import {ConfirmationMessage} from '../confirmation-dialog/confirmation-message'; -import {ConfirmationAcknowledgement} from '../confirmation-dialog/confirmation-state-message'; -import {Tag} from '../service/interface'; -import {GridViewComponent} from '../gridview/grid-view.component'; -import {OperationService} from "../operation/operation.service"; -import {OperateInfo, OperationState, operateChanges} from "../operation/operate"; -import {SERVICE_CONFIG, IServiceConfig, downloadUrl } from '../service.config'; +import { ErrorHandler } from '../error-handler/error-handler'; +import { toPromise, CustomComparator, DEFAULT_PAGE_SIZE, calculatePage, doFiltering, doSorting, clone } from '../utils'; +import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../shared/shared.const'; +import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component'; +import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message'; +import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message'; +import { Tag } from '../service/interface'; +import { GridViewComponent } from '../gridview/grid-view.component'; +import { OperationService } from "../operation/operation.service"; +import { UserPermissionService } from "../service/permission.service"; +import { USERSTATICPERMISSION } from "../service/permission-static"; +import { OperateInfo, OperationState, operateChanges } from "../operation/operate"; +import { SERVICE_CONFIG, IServiceConfig, downloadUrl } from '../service.config'; @Component({ selector: "hbr-repository-gridview", templateUrl: "./repository-gridview.component.html", @@ -80,19 +82,21 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit { confirmationDialog: ConfirmationDialogComponent; @ViewChild("gridView") gridView: GridViewComponent; - + hasCreateRepositoryPermission: boolean; + hasDeleteRepositoryPermission: boolean; constructor(@Inject(SERVICE_CONFIG) private configInfo: IServiceConfig, - private errorHandler: ErrorHandler, - private translateService: TranslateService, - private repositoryService: RepositoryService, - private systemInfoService: SystemInfoService, - private tagService: TagService, - private operationService: OperationService, - private ref: ChangeDetectorRef, - private router: Router) { - if (this.configInfo && this.configInfo.systemInfoEndpoint) { - this.downloadLink = this.configInfo.systemInfoEndpoint + "/getcert"; - } + private errorHandler: ErrorHandler, + private translateService: TranslateService, + private repositoryService: RepositoryService, + private systemInfoService: SystemInfoService, + private tagService: TagService, + private operationService: OperationService, + public userPermissionService: UserPermissionService, + private ref: ChangeDetectorRef, + private router: Router) { + if (this.configInfo && this.configInfo.systemInfoEndpoint) { + this.downloadLink = this.configInfo.systemInfoEndpoint + "/getcert"; + } } public get registryUrl(): string { @@ -142,6 +146,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit { } this.lastFilteredRepoName = ""; + this.getHelmChartVersionPermission(this.projectId); } confirmDeletion(message: ConfirmationAcknowledgement) { @@ -182,8 +187,8 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit { if (this.signedCon[repo.name].length !== 0) { forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), this.translateService.get('REPOSITORY.DELETION_TITLE_REPO_SIGNED')).subscribe(res => { - operateChanges(operMessage, OperationState.failure, res[1]); - }); + operateChanges(operMessage, OperationState.failure, res[1]); + }); } else { return toPromise(this.repositoryService .deleteRepository(repo.name)) @@ -193,24 +198,24 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit { operateChanges(operMessage, OperationState.success); }); }).catch(error => { - if (error.status === "412") { - forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), - this.translateService.get('REPOSITORY.TAGS_SIGNED')).subscribe(res => { - operateChanges(operMessage, OperationState.failure, res[1]); + if (error.status === "412") { + forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), + this.translateService.get('REPOSITORY.TAGS_SIGNED')).subscribe(res => { + operateChanges(operMessage, OperationState.failure, res[1]); + }); + return; + } + if (error.status === 503) { + forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), + this.translateService.get('REPOSITORY.TAGS_NO_DELETE')).subscribe(res => { + operateChanges(operMessage, OperationState.failure, res[1]); + }); + return; + } + this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => { + operateChanges(operMessage, OperationState.failure, res); }); - return; - } - if (error.status === 503) { - forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), - this.translateService.get('REPOSITORY.TAGS_NO_DELETE')).subscribe(res => { - operateChanges(operMessage, OperationState.failure, res[1]); - }); - return; - } - this.translateService.get('BATCH.DELETED_FAILURE').subscribe(res => { - operateChanges(operMessage, OperationState.failure, res); }); - }); } } @@ -219,7 +224,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit { this.currentPage = 1; let st: State = this.currentState; if (!st) { - st = {page: {}}; + st = { page: {} }; } st.page.size = this.pageSize; st.page.from = 0; @@ -299,8 +304,8 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit { return toPromise(this.tagService.getTags(repo.name)) .then(items => { if (items.some((t: Tag) => { - return t.name === 'latest'; - })) { + return t.name === 'latest'; + })) { return true; } else { return false; @@ -449,7 +454,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit { let st: State = this.currentState; if (!st) { - st = {page: {}}; + st = { page: {} }; } st.page.size = this.pageSize; st.page.from = (targetPageNumber - 1) * this.pageSize; @@ -497,4 +502,16 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit { return this.listHover; } } + + getHelmChartVersionPermission(projectId: number): void { + + let hasCreateRepositoryPermission = this.userPermissionService.getPermission(this.projectId, + USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.CREATE); + let hasDeleteRepositoryPermission = this.userPermissionService.getPermission(this.projectId, + USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.DELETE); + forkJoin(hasCreateRepositoryPermission, hasDeleteRepositoryPermission).subscribe(permissions => { + this.hasCreateRepositoryPermission = permissions[0] as boolean; + this.hasDeleteRepositoryPermission = permissions[1] as boolean; + }, error => this.errorHandler.error(error)); + } } diff --git a/src/portal/lib/src/repository/repository.component.html b/src/portal/lib/src/repository/repository.component.html index 207ea6d69..a5216ed33 100644 --- a/src/portal/lib/src/repository/repository.component.html +++ b/src/portal/lib/src/repository/repository.component.html @@ -55,7 +55,7 @@
diff --git a/src/portal/lib/src/repository/repository.component.spec.ts b/src/portal/lib/src/repository/repository.component.spec.ts index 58ff2b932..7b90f9387 100644 --- a/src/portal/lib/src/repository/repository.component.spec.ts +++ b/src/portal/lib/src/repository/repository.component.spec.ts @@ -27,6 +27,7 @@ import { LabelPieceComponent } from "../label-piece/label-piece.component"; import { LabelDefaultService, LabelService } from "../service/label.service"; import { OperationService } from "../operation/operation.service"; import { ProjectDefaultService, ProjectService, RetagDefaultService, RetagService } from "../service"; +import { UserPermissionDefaultService, UserPermissionService } from "../service/permission.service"; class RouterStub { @@ -178,6 +179,7 @@ describe('RepositoryComponent (inline template)', () => { { provide: ProjectService, useClass: ProjectDefaultService }, { provide: RetagService, useClass: RetagDefaultService }, { provide: LabelService, useClass: LabelDefaultService}, + { provide: UserPermissionService, useClass: UserPermissionDefaultService}, { provide: ChannelService}, { provide: OperationService } ] diff --git a/src/portal/lib/src/service/index.ts b/src/portal/lib/src/service/index.ts index afc38321a..1772c0054 100644 --- a/src/portal/lib/src/service/index.ts +++ b/src/portal/lib/src/service/index.ts @@ -13,3 +13,5 @@ export * from "./project.service"; export * from "./label.service"; export * from "./helm-chart.service"; export * from "./retag.service"; +export * from "./permission.service"; +export * from "./permission-static"; diff --git a/src/portal/lib/src/service/interface.ts b/src/portal/lib/src/service/interface.ts index 0b07259d2..7bd1c8993 100644 --- a/src/portal/lib/src/service/interface.ts +++ b/src/portal/lib/src/service/interface.ts @@ -119,8 +119,8 @@ export class Trigger { schedule_param: | any | { - [key: string]: any | any[]; - }; + [key: string]: any | any[]; + }; constructor(kind: string, param: any | { [key: string]: any | any[] }) { this.kind = kind; this.schedule_param = param; @@ -395,8 +395,8 @@ export interface HelmChartSignature { * interface Manifest */ export interface Manifest { - manifset: Object; - config: string; + manifset: Object; + config: string; } export interface RetagRequest { @@ -426,10 +426,23 @@ export interface ClrDatagridFilterInterface { } /** @deprecated since 0.11 */ -export interface Comparator extends ClrDatagridComparatorInterface {} +export interface Comparator extends ClrDatagridComparatorInterface { } /** @deprecated since 0.11 */ -export interface ClrFilter extends ClrDatagridFilterInterface {} +export interface ClrFilter extends ClrDatagridFilterInterface { } /** @deprecated since 0.11 */ -export interface State extends ClrDatagridStateInterface {} -export interface Modal extends ClrModal {} +export interface State extends ClrDatagridStateInterface { } +export interface Modal extends ClrModal { } export const Modal = ClrModal; + +/** + * The access user privilege from serve. + * + ** + * interface UserPrivilegeServe + */ +export interface UserPrivilegeServeItem { + [key: string]: any | any[]; + resource: string; + action: string; +} + diff --git a/src/portal/lib/src/service/permission-static.ts b/src/portal/lib/src/service/permission-static.ts new file mode 100644 index 000000000..da0aaf62c --- /dev/null +++ b/src/portal/lib/src/service/permission-static.ts @@ -0,0 +1,134 @@ +export const USERSTATICPERMISSION = { + "PROJECT": { + 'KEY': 'project', + 'VALUE': { + "DELETE": "delete" + } + }, + "MEMBER": { + 'KEY': 'member', + 'VALUE': { + "CREATE": "create", + "UPDATE": "update", + "DELETE": "delete", + "LIST": "list" + } + }, + "LOG": { + 'KEY': 'log', + 'VALUE': { + "LIST": "list" + } + }, + "REPLICATION": { + 'KEY': 'replication', + 'VALUE': { + "CREATE": "create", + "UPDATE": "update", + "DELETE": "delete", + "LIST": "list", + } + }, + "REPLICATION_JOB": { + 'KEY': 'replication-job', + 'VALUE': { + "CREATE": "create", + } + }, + "LABEL": { + 'KEY': 'label', + 'VALUE': { + "CREATE": "create", + "UPDATE": "update", + "DELETE": "delete", + "LIST": "list", + } + }, + "CONFIGURATION": { + 'KEY': 'configuration', + 'VALUE': { + "UPDATE": "update", + "READ": "read", + } + }, + "REPOSITORY": { + 'KEY': 'repository', + 'VALUE': { + "CREATE": "create", + "UPDATE": "update", + "DELETE": "delete", + "LIST": "list", + "PUSH": "push", + "PULL": "pull", + } + }, + "REPOSITORY_TAG": { + 'KEY': 'repository-tag', + 'VALUE': { + "DELETE": "delete", + "LIST": "list", + } + }, + "REPOSITORY_TAG_SCAN_JOB": { + 'KEY': 'repository-tag-scan-job', + 'VALUE': { + "CREATE": "create", + "READ": "read", + "LIST": "list", + } + }, + "REPOSITORY_TAG_VULNERABILITY": { + 'KEY': 'repository-tag-vulnerability', + 'VALUE': { + "LIST": "list", + } + }, + "REPOSITORY_TAG_LABEL": { + 'KEY': 'repository-tag-label', + 'VALUE': { + "CREATE": "create", + "DELETE": "delete", + } + }, + "REPOSITORY_TAG_MANIFEST": { + 'KEY': 'repository-tag-manifest', + 'VALUE': { + "READ": "read", + } + }, + "HELM_CHART": { + 'KEY': 'helm-chart', + 'VALUE': { + "UPLOAD": "create", + "DOWNLOAD": "read", + "DELETE": "delete", + "LIST": "list", + } + }, + "HELM_CHART_VERSION": { + 'KEY': 'helm-chart-version', + 'VALUE': { + "DELETE": "delete", + "LIST": "list", + "READ": "read", + } + }, + "HELM_CHART_VERSION_LABEL": { + 'KEY': 'helm-chart-version-label', + 'VALUE': { + "CREATE": "create", + "DELETE": "delete", + } + }, + "ROBOT": { + 'KEY': 'robot', + 'VALUE': { + "CREATE": "create", + "UPDATE": "update", + "DELETE": "delete", + "LIST": "list", + "READ": "read", + } + }, +}; + diff --git a/src/portal/lib/src/service/permission.service.ts b/src/portal/lib/src/service/permission.service.ts new file mode 100644 index 000000000..0130c20af --- /dev/null +++ b/src/portal/lib/src/service/permission.service.ts @@ -0,0 +1,76 @@ +// Copyright (c) 2017 VMware, Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { Observable, throwError as observableThrowError } from "rxjs"; +import { map, catchError, shareReplay } from "rxjs/operators"; +import { UserPrivilegeServeItem } from './interface'; +import { HttpClient } from '@angular/common/http'; + + + +const CACHE_SIZE = 1; +/** + * Get System privilege about current backend server. + * @abstract + * class UserPermissionService + */ + +export abstract class UserPermissionService { + /** + * Get user privilege information. + * @abstract + * returns + */ + abstract getPermission(projectId, resource, action); + abstract clearPermissionCache(); +} + +@Injectable() +export class UserPermissionDefaultService extends UserPermissionService { + constructor( + private http: HttpClient, + ) { + super(); + } + private permissionCache: Observable; + private getPermissionFromBackend(projectId): Observable { + const userPermissionUrl = `/api/users/current/permissions?scope=/project/${projectId}&relative=true`; + return this.http.get(userPermissionUrl); + } + private processingPermissionResult(responsePermission, resource, action): boolean { + const permissionList = responsePermission as UserPrivilegeServeItem[]; + for (const privilegeItem of permissionList) { + if (privilegeItem.resource === resource && privilegeItem.action === action) { + return true; + } + } + return false; + } + public getPermission(projectId, resource, action): Observable { + + if (!this.permissionCache) { + this.permissionCache = this.getPermissionFromBackend(projectId).pipe( + shareReplay(CACHE_SIZE)); + } + return this.permissionCache.pipe(map(response => { + return this.processingPermissionResult(response, resource, action); + })) + .pipe(catchError(error => observableThrowError(error) + )); + } + public clearPermissionCache() { + this.permissionCache = null; + } +} diff --git a/src/portal/lib/src/shared/shared.const.ts b/src/portal/lib/src/shared/shared.const.ts index 3c6c83fee..f180073ad 100644 --- a/src/portal/lib/src/shared/shared.const.ts +++ b/src/portal/lib/src/shared/shared.const.ts @@ -91,12 +91,14 @@ export const LabelColor = [ { 'color': '#F57600', 'textColor': 'black' }, { 'color': '#FFDC0B', 'textColor': 'black' }, ]; -export const RoleMapping = { 'projectAdmin': 'MEMBER.PROJECT_ADMIN', 'developer': 'MEMBER.DEVELOPER', 'guest': 'MEMBER.GUEST' }; +export const RoleMapping = { 'projectAdmin': 'MEMBER.PROJECT_ADMIN', 'master': 'MEMBER.PROJECT_MASTER', +'developer': 'MEMBER.DEVELOPER', 'guest': 'MEMBER.GUEST' }; export const DefaultHelmIcon = '/images/helm-gray.svg'; export enum Roles { PROJECT_ADMIN = 1, + PROJECT_MASTER = 4, DEVELOPER = 2, GUEST = 3, OTHER = 0, diff --git a/src/portal/lib/src/tag/tag-detail.component.html b/src/portal/lib/src/tag/tag-detail.component.html index 70929ddd8..9fb78661a 100644 --- a/src/portal/lib/src/tag/tag-detail.component.html +++ b/src/portal/lib/src/tag/tag-detail.component.html @@ -91,15 +91,15 @@ - + - + - {{ 'REPOSITORY.BUILD_HISTORY' | diff --git a/src/portal/lib/src/tag/tag-detail.component.spec.ts b/src/portal/lib/src/tag/tag-detail.component.spec.ts index 17c59bef1..64a95b26b 100644 --- a/src/portal/lib/src/tag/tag-detail.component.spec.ts +++ b/src/portal/lib/src/tag/tag-detail.component.spec.ts @@ -25,15 +25,19 @@ import { VULNERABILITY_SCAN_STATUS } from "../utils"; import { VULNERABILITY_DIRECTIVES } from "../vulnerability-scanning/index"; import { LabelPieceComponent } from "../label-piece/label-piece.component"; import { ChannelService } from "../channel/channel.service"; +import { of } from "rxjs"; import { JobLogService, JobLogDefaultService } from "../service/job-log.service"; +import { UserPermissionService, UserPermissionDefaultService } from "../service/permission.service"; +import { USERSTATICPERMISSION } from "../service/permission-static"; describe("TagDetailComponent (inline template)", () => { let comp: TagDetailComponent; let fixture: ComponentFixture; let tagService: TagService; + let userPermissionService: UserPermissionService; let scanningService: ScanningResultService; let spy: jasmine.Spy; let vulSpy: jasmine.Spy; @@ -83,13 +87,13 @@ describe("TagDetailComponent (inline template)", () => { let config: IServiceConfig = { repositoryBaseEndpoint: "/api/repositories/testing" }; - + let mockHasVulnerabilitiesListPermission: boolean = false; + let mockHasBuildHistoryPermission: boolean = true; let mockManifest: Manifest = { manifset: {}, // tslint:disable-next-line:max-line-length config: `{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh"],"ArgsEscaped":true,"Image":"sha256:fbef17698ac8605733924d5662f0cbfc0b27a51e83ab7d7a4b8d8a9a9fe0d1c2","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"30e1a2427aa2325727a092488d304505780501585a6ccf5a6a53c4d83a826101","container_config":{"Hostname":"30e1a2427aa2","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\\"/bin/sh\\"]"],"ArgsEscaped":true,"Image":"sha256:fbef17698ac8605733924d5662f0cbfc0b27a51e83ab7d7a4b8d8a9a9fe0d1c2","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2018-01-09T21:10:58.579708634Z","docker_version":"17.06.2-ce","history":[{"created":"2018-01-09T21:10:58.365737589Z","created_by":"/bin/sh -c #(nop) ADD file:093f0723fa46f6cdbd6f7bd146448bb70ecce54254c35701feeceb956414622f in / "},{"created":"2018-01-09T21:10:58.579708634Z","created_by":"/bin/sh -c #(nop) CMD [\\"/bin/sh\\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:cd7100a72410606589a54b932cabd804a17f9ae5b42a1882bd56d263e02b6215"]}}` }; - beforeEach(async(() => { TestBed.configureTestingModule({ imports: [SharedModule], @@ -108,6 +112,7 @@ describe("TagDetailComponent (inline template)", () => { { provide: JobLogService, useClass: JobLogDefaultService }, { provide: SERVICE_CONFIG, useValue: config }, { provide: TagService, useClass: TagDefaultService }, + { provide: UserPermissionService, useClass: UserPermissionDefaultService }, { provide: ScanningResultService, useClass: ScanningResultDefaultService @@ -122,6 +127,8 @@ describe("TagDetailComponent (inline template)", () => { comp.tagId = "mock_tag"; comp.repositoryId = "mock_repo"; + comp.projectId = 1; + tagService = fixture.debugElement.injector.get(TagService); spy = spyOn(tagService, "getTag").and.returnValues( @@ -153,7 +160,14 @@ describe("TagDetailComponent (inline template)", () => { manifestSpy = spyOn(tagService, "getManifest").and.returnValues( Promise.resolve(mockManifest) ); + userPermissionService = fixture.debugElement.injector.get(UserPermissionService); + spyOn(userPermissionService, "getPermission") + .withArgs(comp.projectId, + USERSTATICPERMISSION.REPOSITORY_TAG_VULNERABILITY.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_VULNERABILITY.VALUE.LIST ) + .and.returnValue(of(mockHasVulnerabilitiesListPermission)) + .withArgs(comp.projectId, USERSTATICPERMISSION.REPOSITORY_TAG_MANIFEST.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_MANIFEST.VALUE.READ ) + .and.returnValue(of(mockHasBuildHistoryPermission)); fixture.detectChanges(); }); diff --git a/src/portal/lib/src/tag/tag-detail.component.ts b/src/portal/lib/src/tag/tag-detail.component.ts index af92d942a..79e001722 100644 --- a/src/portal/lib/src/tag/tag-detail.component.ts +++ b/src/portal/lib/src/tag/tag-detail.component.ts @@ -4,6 +4,9 @@ import { TagService, Tag, VulnerabilitySeverity } from "../service/index"; import { toPromise } from "../utils"; import { ErrorHandler } from "../error-handler/index"; import { Label } from "../service/interface"; +import { forkJoin } from "rxjs"; +import { UserPermissionService } from "../service/permission.service"; +import { USERSTATICPERMISSION } from "../service/permission-static"; const TabLinkContentMap: { [index: string]: string } = { "tag-history": "history", @@ -32,8 +35,6 @@ export class TagDetailComponent implements OnInit { withAdmiral: boolean; @Input() withClair: boolean; - @Input() - withAdminRole: boolean; tagDetails: Tag = { name: "--", size: "--", @@ -51,11 +52,14 @@ export class TagDetailComponent implements OnInit { backEvt: EventEmitter = new EventEmitter(); currentTabID = "tag-vulnerability"; - + hasVulnerabilitiesListPermission: boolean; + hasBuildHistoryPermission: boolean; + @Input() projectId: number; constructor( private tagService: TagService, - private errorHandler: ErrorHandler - ) {} + private errorHandler: ErrorHandler, + private userPermissionService: UserPermissionService, + ) { } ngOnInit(): void { if (this.repositoryId && this.tagId) { @@ -90,6 +94,7 @@ export class TagDetailComponent implements OnInit { }) .catch(error => this.errorHandler.error(error)); } + this.getTagPermissions(this.projectId); } onBack(): void { @@ -173,4 +178,16 @@ export class TagDetailComponent implements OnInit { tabLinkClick(tabID: string) { this.currentTabID = tabID; } + + getTagPermissions(projectId: number): void { + + const hasVulnerabilitiesListPermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.REPOSITORY_TAG_VULNERABILITY.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_VULNERABILITY.VALUE.LIST); + const hasBuildHistoryPermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.REPOSITORY_TAG_MANIFEST.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_MANIFEST.VALUE.READ); + forkJoin(hasVulnerabilitiesListPermission, hasBuildHistoryPermission).subscribe(permissions => { + this.hasVulnerabilitiesListPermission = permissions[0] as boolean; + this.hasBuildHistoryPermission = permissions[1] as boolean; + }, error => this.errorHandler.error(error)); + } } diff --git a/src/portal/lib/src/tag/tag.component.html b/src/portal/lib/src/tag/tag.component.html index 10a450d54..dd0f462a9 100644 --- a/src/portal/lib/src/tag/tag.component.html +++ b/src/portal/lib/src/tag/tag.component.html @@ -60,7 +60,7 @@ - +
@@ -75,8 +75,8 @@
- - + + {{'REPOSITORY.TAG' | translate}} {{'REPOSITORY.SIZE' | translate}} diff --git a/src/portal/lib/src/tag/tag.component.spec.ts b/src/portal/lib/src/tag/tag.component.spec.ts index 0e9429b41..22811acc3 100644 --- a/src/portal/lib/src/tag/tag.component.spec.ts +++ b/src/portal/lib/src/tag/tag.component.spec.ts @@ -20,16 +20,21 @@ import { ChannelService } from "../channel/index"; import { CopyInputComponent } from "../push-image/copy-input.component"; import { LabelPieceComponent } from "../label-piece/label-piece.component"; import { LabelDefaultService, LabelService } from "../service/label.service"; +import { UserPermissionService, UserPermissionDefaultService } from "../service/permission.service"; +import { USERSTATICPERMISSION } from "../service/permission-static"; import { OperationService } from "../operation/operation.service"; +import { Observable, of } from "rxjs"; describe("TagComponent (inline template)", () => { let comp: TagComponent; let fixture: ComponentFixture; let tagService: TagService; + let userPermissionService: UserPermissionService; let spy: jasmine.Spy; let spyLabels: jasmine.Spy; let spyLabels1: jasmine.Spy; + let mockTags: Tag[] = [ { "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", @@ -95,7 +100,10 @@ describe("TagComponent (inline template)", () => { let config: IServiceConfig = { repositoryBaseEndpoint: "/api/repositories/testing" }; - + let mockHasAddLabelImagePermission: boolean = true; + let mockHasRetagImagePermission: boolean = true; + let mockHasDeleteImagePermission: boolean = true; + let mockHasScanImagePermission: boolean = true; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ @@ -119,6 +127,7 @@ describe("TagComponent (inline template)", () => { { provide: RetagService, useClass: RetagDefaultService }, { provide: ScanningResultService, useClass: ScanningResultDefaultService }, { provide: LabelService, useClass: LabelDefaultService }, + { provide: UserPermissionService, useClass: UserPermissionDefaultService }, { provide: OperationService } ] }); @@ -130,10 +139,12 @@ describe("TagComponent (inline template)", () => { comp.projectId = 1; comp.repoName = "library/nginx"; - comp.hasProjectAdminRole = true; + comp.hasDeleteImagePermission = true; + comp.hasScanImagePermission = true; comp.hasSignedIn = true; comp.registryUrl = "http://registry.testing.com"; comp.withNotary = false; + comp.withAdmiral = false; let labelService: LabelService; @@ -141,6 +152,17 @@ describe("TagComponent (inline template)", () => { tagService = fixture.debugElement.injector.get(TagService); spy = spyOn(tagService, "getTags").and.returnValues(Promise.resolve(mockTags)); + userPermissionService = fixture.debugElement.injector.get(UserPermissionService); + + spyOn(userPermissionService, "getPermission") + .withArgs(comp.projectId, USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.VALUE.CREATE ) + .and.returnValue(of(mockHasAddLabelImagePermission)) + .withArgs(comp.projectId, USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.PULL ) + .and.returnValue(of(mockHasRetagImagePermission)) + .withArgs(comp.projectId, USERSTATICPERMISSION.REPOSITORY_TAG.KEY, USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE ) + .and.returnValue(of(mockHasDeleteImagePermission)) + .withArgs(comp.projectId, USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE) + .and.returnValue(of(mockHasScanImagePermission)); labelService = fixture.debugElement.injector.get(LabelService); @@ -149,7 +171,6 @@ describe("TagComponent (inline template)", () => { fixture.detectChanges(); }); - it("should load data", async(() => { expect(spy.calls.any).toBeTruthy(); })); @@ -169,3 +190,5 @@ describe("TagComponent (inline template)", () => { })); }); + + diff --git a/src/portal/lib/src/tag/tag.component.ts b/src/portal/lib/src/tag/tag.component.ts index 69b389e53..09ef90dda 100644 --- a/src/portal/lib/src/tag/tag.component.ts +++ b/src/portal/lib/src/tag/tag.component.ts @@ -21,8 +21,8 @@ import { ChangeDetectorRef, ElementRef, AfterViewInit } from "@angular/core"; -import {Subject, forkJoin} from "rxjs"; -import { debounceTime , distinctUntilChanged, finalize} from 'rxjs/operators'; +import { Subject, forkJoin } from "rxjs"; +import { debounceTime, distinctUntilChanged, finalize } from 'rxjs/operators'; import { TranslateService } from "@ngx-translate/core"; import { State, Comparator } from "../service/interface"; @@ -30,9 +30,9 @@ import { TagService, RetagService, VulnerabilitySeverity, RequestQueryParams } f import { ErrorHandler } from "../error-handler/error-handler"; import { ChannelService } from "../channel/index"; import { - ConfirmationTargets, - ConfirmationState, - ConfirmationButtons, Roles + ConfirmationTargets, + ConfirmationState, + ConfirmationButtons, Roles } from "../shared/shared.const"; import { ConfirmationDialogComponent } from "../confirmation-dialog/confirmation-dialog.component"; @@ -54,6 +54,8 @@ import { import { CopyInputComponent } from "../push-image/copy-input.component"; import { LabelService } from "../service/label.service"; +import { UserPermissionService } from "../service/permission.service"; +import { USERSTATICPERMISSION } from "../service/permission-static"; import { operateChanges, OperateInfo, OperationState } from "../operation/operate"; import { OperationService } from "../operation/operation.service"; import { ImageNameInputComponent } from "../image-name-input/image-name-input.component"; @@ -71,14 +73,13 @@ export interface LabelState { }) export class TagComponent implements OnInit, AfterViewInit { - signedCon: {[key: string]: any | string[]} = {}; + signedCon: { [key: string]: any | string[] } = {}; @Input() projectId: number; @Input() memberRoleID: number; @Input() repoName: string; @Input() isEmbedded: boolean; @Input() hasSignedIn: boolean; - @Input() hasProjectAdminRole: boolean; @Input() isGuest: boolean; @Input() registryUrl: string; @Input() withNotary: boolean; @@ -116,8 +117,8 @@ export class TagComponent implements OnInit, AfterViewInit { labelListOpen = false; selectedTag: Tag[]; - labelNameFilter: Subject = new Subject (); - stickLabelNameFilter: Subject = new Subject (); + labelNameFilter: Subject = new Subject(); + stickLabelNameFilter: Subject = new Subject(); filterOnGoing: boolean; stickName = ''; filterName = ''; @@ -144,10 +145,15 @@ export class TagComponent implements OnInit, AfterViewInit { totalCount = 0; currentState: State; + hasAddLabelImagePermission: boolean; + hasRetagImagePermission: boolean; + hasDeleteImagePermission: boolean; + hasScanImagePermission: boolean; constructor( private errorHandler: ErrorHandler, private tagService: TagService, private retagService: RetagService, + private userPermissionService: UserPermissionService, private labelService: LabelService, private translateService: TranslateService, private ref: ChangeDetectorRef, @@ -164,50 +170,50 @@ export class TagComponent implements OnInit, AfterViewInit { this.errorHandler.error("Repo name cannot be unset."); return; } - this.retrieve(); this.lastFilteredTagName = ''; this.labelNameFilter - .pipe(debounceTime(500)) - .pipe(distinctUntilChanged()) - .subscribe((name: string) => { - if (this.filterName.length) { - this.filterOnGoing = true; + .pipe(debounceTime(500)) + .pipe(distinctUntilChanged()) + .subscribe((name: string) => { + if (this.filterName.length) { + this.filterOnGoing = true; - this.imageFilterLabels.forEach(data => { - if (data.label.name.indexOf(this.filterName) !== -1) { - data.show = true; - } else { - data.show = false; - } - }); - setTimeout(() => { - setInterval(() => this.ref.markForCheck(), 200); - }, 1000); - } - }); + this.imageFilterLabels.forEach(data => { + if (data.label.name.indexOf(this.filterName) !== -1) { + data.show = true; + } else { + data.show = false; + } + }); + setTimeout(() => { + setInterval(() => this.ref.markForCheck(), 200); + }, 1000); + } + }); this.stickLabelNameFilter - .pipe(debounceTime(500)) - .pipe(distinctUntilChanged()) - .subscribe((name: string) => { - if (this.stickName.length) { - this.filterOnGoing = true; + .pipe(debounceTime(500)) + .pipe(distinctUntilChanged()) + .subscribe((name: string) => { + if (this.stickName.length) { + this.filterOnGoing = true; - this.imageStickLabels.forEach(data => { - if (data.label.name.indexOf(this.stickName) !== -1) { - data.show = true; - } else { - data.show = false; - } - }); - setTimeout(() => { - setInterval(() => this.ref.markForCheck(), 200); - }, 1000); - } - }); + this.imageStickLabels.forEach(data => { + if (data.label.name.indexOf(this.stickName) !== -1) { + data.show = true; + } else { + data.show = false; + } + }); + setTimeout(() => { + setInterval(() => this.ref.markForCheck(), 200); + }, 1000); + } + }); + this.getImagePermissionRule(this.projectId); } ngAfterViewInit() { @@ -219,7 +225,7 @@ export class TagComponent implements OnInit, AfterViewInit { public get filterLabelPieceWidth() { let len = this.lastFilteredTagName.length ? this.lastFilteredTagName.length * 6 + 60 : 115; return len > 210 ? 210 : len; -} + } doSearchTagNames(tagName: string) { this.lastFilteredTagName = tagName; @@ -234,9 +240,9 @@ export class TagComponent implements OnInit, AfterViewInit { st.page.to = this.pageSize - 1; let selectedLab = this.imageFilterLabels.find(label => label.iconsShow === true); if (selectedLab) { - st.filters = [{property: 'name', value: this.lastFilteredTagName}, {property: 'labels.id', value: selectedLab.label.id}]; + st.filters = [{ property: 'name', value: this.lastFilteredTagName }, { property: 'labels.id', value: selectedLab.label.id }]; } else { - st.filters = [{property: 'name', value: this.lastFilteredTagName}]; + st.filters = [{ property: 'name', value: this.lastFilteredTagName }]; } this.clrLoad(st); @@ -286,14 +292,14 @@ export class TagComponent implements OnInit, AfterViewInit { toPromise(this.labelService.getGLabels()).then((res: Label[]) => { if (res.length) { res.forEach(data => { - this.imageLabels.push({'iconsShow': false, 'label': data, 'show': true}); + this.imageLabels.push({ 'iconsShow': false, 'label': data, 'show': true }); }); } toPromise(this.labelService.getPLabels(this.projectId)).then((res1: Label[]) => { if (res1.length) { res1.forEach(data => { - this.imageLabels.push({'iconsShow': false, 'label': data, 'show': true}); + this.imageLabels.push({ 'iconsShow': false, 'label': data, 'show': true }); }); } this.imageFilterLabels = clone(this.imageLabels); @@ -372,15 +378,15 @@ export class TagComponent implements OnInit, AfterViewInit { } unSelectLabel(labelInfo: LabelState): void { - if (!this.inprogress) { - this.inprogress = true; - let labelId = labelInfo.label.id; - this.selectedRow = this.selectedTag; - toPromise(this.tagService.deleteLabelToImages(this.repoName, this.selectedRow[0].name, labelId)).then(res => { - this.refresh(); + if (!this.inprogress) { + this.inprogress = true; + let labelId = labelInfo.label.id; + this.selectedRow = this.selectedTag; + toPromise(this.tagService.deleteLabelToImages(this.repoName, this.selectedRow[0].name, labelId)).then(res => { + this.refresh(); - // insert the unselected label to groups with the same icons - this.sortOperation(this.imageStickLabels, labelInfo); + // insert the unselected label to groups with the same icons + this.sortOperation(this.imageStickLabels, labelInfo); labelInfo.iconsShow = false; this.inprogress = false; }).catch(err => { @@ -417,26 +423,26 @@ export class TagComponent implements OnInit, AfterViewInit { data.iconsShow = true; } }); - this.imageFilterLabels.splice(this.imageFilterLabels.indexOf(labelInfo), 1); - this.imageFilterLabels.unshift(labelInfo); - this.filterOneLabel = labelInfo.label; + this.imageFilterLabels.splice(this.imageFilterLabels.indexOf(labelInfo), 1); + this.imageFilterLabels.unshift(labelInfo); + this.filterOneLabel = labelInfo.label; - // reload data - this.currentPage = 1; - let st: State = this.currentState; - if (!st) { - st = { page: {} }; - } - st.page.size = this.pageSize; - st.page.from = 0; - st.page.to = this.pageSize - 1; - if (this.lastFilteredTagName) { - st.filters = [{property: 'name', value: this.lastFilteredTagName}, {property: 'labels.id', value: labelId}]; - } else { - st.filters = [{property: 'labels.id', value: labelId}]; - } + // reload data + this.currentPage = 1; + let st: State = this.currentState; + if (!st) { + st = { page: {} }; + } + st.page.size = this.pageSize; + st.page.from = 0; + st.page.to = this.pageSize - 1; + if (this.lastFilteredTagName) { + st.filters = [{ property: 'name', value: this.lastFilteredTagName }, { property: 'labels.id', value: labelId }]; + } else { + st.filters = [{ property: 'labels.id', value: labelId }]; + } - this.clrLoad(st); + this.clrLoad(st); } unFilterLabel(labelInfo: LabelState): void { @@ -456,7 +462,7 @@ export class TagComponent implements OnInit, AfterViewInit { st.page.from = 0; st.page.to = this.pageSize - 1; if (this.lastFilteredTagName) { - st.filters = [{property: 'name', value: this.lastFilteredTagName}]; + st.filters = [{ property: 'name', value: this.lastFilteredTagName }]; } else { st.filters = []; } @@ -480,7 +486,7 @@ export class TagComponent implements OnInit, AfterViewInit { data.show = false; } }); - } else { + } else { this.openLabelFilterPanel = false; this.openLabelFilterPiece = false; } @@ -523,7 +529,7 @@ export class TagComponent implements OnInit, AfterViewInit { retrieve() { this.tags = []; - let signatures: string[] = [] ; + let signatures: string[] = []; this.loading = true; toPromise(this.tagService @@ -539,15 +545,15 @@ export class TagComponent implements OnInit, AfterViewInit { components: { total: 0, summary: [] - } - }; - } - if (t.signature !== null) { - signatures.push(t.name); - } - }); - this.tags = items; - let signedName: {[key: string]: string[]} = {}; + } + }; + } + 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; @@ -568,9 +574,9 @@ export class TagComponent implements OnInit, AfterViewInit { if (Math.pow(1024, 1) <= size && size < Math.pow(1024, 2)) { return (size / Math.pow(1024, 1)).toFixed(2) + "KB"; } else if (Math.pow(1024, 2) <= size && size < Math.pow(1024, 3)) { - return (size / Math.pow(1024, 2)).toFixed(2) + "MB"; + return (size / Math.pow(1024, 2)).toFixed(2) + "MB"; } else if (Math.pow(1024, 3) <= size && size < Math.pow(1024, 4)) { - return (size / Math.pow(1024, 3)).toFixed(2) + "GB"; + return (size / Math.pow(1024, 3)).toFixed(2) + "GB"; } else { return size + "B"; } @@ -578,8 +584,8 @@ export class TagComponent implements OnInit, AfterViewInit { retag(tags: Tag[]) { if (tags && tags.length) { - this.retagDialogOpened = true; - this.retagSrcImage = this.repoName + ":" + tags[0].digest; + this.retagDialogOpened = true; + this.retagSrcImage = this.repoName + ":" + tags[0].digest; } else { this.errorHandler.error("One tag should be selected before retag."); } @@ -587,23 +593,23 @@ export class TagComponent implements OnInit, AfterViewInit { onRetag() { this.retagService.retag({ - targetProject: this.imageNameInput.projectName.value, - targetRepo: this.imageNameInput.repoName.value, - targetTag: this.imageNameInput.tagName.value, - srcImage: this.retagSrcImage, - override: true - }) - .pipe(finalize(() => { + targetProject: this.imageNameInput.projectName.value, + targetRepo: this.imageNameInput.repoName.value, + targetTag: this.imageNameInput.tagName.value, + srcImage: this.retagSrcImage, + override: true + }) + .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 => { + })) + .subscribe(response => { + this.translateService.get('RETAG.MSG_SUCCESS').subscribe((res: string) => { + this.errorHandler.info(res); + }); + }, error => { this.errorHandler.error(error); - }); + }); } deleteTags(tags: Tag[]) { @@ -631,8 +637,8 @@ export class TagComponent implements OnInit, AfterViewInit { confirmDeletion(message: ConfirmationAcknowledgement) { if (message && - message.source === ConfirmationTargets.TAG - && message.state === ConfirmationState.CONFIRMED) { + message.source === ConfirmationTargets.TAG + && message.state === ConfirmationState.CONFIRMED) { let tags: Tag[] = message.data; if (tags && tags.length) { let promiseLists: any[] = []; @@ -660,27 +666,27 @@ export class TagComponent implements OnInit, AfterViewInit { if (tag.signature) { forkJoin(this.translateService.get("BATCH.DELETED_FAILURE"), this.translateService.get("REPOSITORY.DELETION_SUMMARY_TAG_DENIED")).subscribe(res => { - let wrongInfo: string = res[1] + "notary -s https://" + this.registryUrl + + let wrongInfo: string = res[1] + "notary -s https://" + this.registryUrl + ":4443 -d ~/.docker/trust remove -p " + this.registryUrl + "/" + this.repoName + " " + name; - operateChanges(operMessage, OperationState.failure, wrongInfo); - }); + operateChanges(operMessage, OperationState.failure, wrongInfo); + }); } else { return toPromise(this.tagService - .deleteTag(this.repoName, tag.name)) - .then( - response => { - this.translateService.get("BATCH.DELETED_SUCCESS") - .subscribe(res => { - operateChanges(operMessage, OperationState.success); - }); - }).catch(error => { + .deleteTag(this.repoName, tag.name)) + .then( + response => { + this.translateService.get("BATCH.DELETED_SUCCESS") + .subscribe(res => { + operateChanges(operMessage, OperationState.success); + }); + }).catch(error => { if (error.status === 503) { forkJoin(this.translateService.get('BATCH.DELETED_FAILURE'), - this.translateService.get('REPOSITORY.TAGS_NO_DELETE')).subscribe(res => { - operateChanges(operMessage, OperationState.failure, res[1]); - }); + this.translateService.get('REPOSITORY.TAGS_NO_DELETE')).subscribe(res => { + operateChanges(operMessage, OperationState.failure, res[1]); + }); return; } this.translateService.get("BATCH.DELETED_FAILURE").subscribe(res => { @@ -744,13 +750,29 @@ export class TagComponent implements OnInit, AfterViewInit { // Whether show the 'scan now' menu canScanNow(t: Tag[]): boolean { if (!this.withClair) { return false; } - if (!this.hasProjectAdminRole) { return false; } - let st: string = this.scanStatus(t[0]); + if (!this.hasScanImagePermission) { return false; } + let st: string = this.scanStatus(t[0]); return st !== VULNERABILITY_SCAN_STATUS.pending && st !== VULNERABILITY_SCAN_STATUS.running; } - + getImagePermissionRule(projectId: number): void { + let hasAddLabelImagePermission = this.userPermissionService.getPermission(projectId, USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.KEY, + USERSTATICPERMISSION.REPOSITORY_TAG_LABEL.VALUE.CREATE); + let hasRetagImagePermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.PULL); + let hasDeleteImagePermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.REPOSITORY_TAG.KEY, USERSTATICPERMISSION.REPOSITORY_TAG.VALUE.DELETE); + let hasScanImagePermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE); + forkJoin(hasAddLabelImagePermission, hasRetagImagePermission, hasDeleteImagePermission, hasScanImagePermission) + .subscribe(permissions => { + this.hasAddLabelImagePermission = permissions[0] as boolean; + this.hasRetagImagePermission = permissions[1] as boolean; + this.hasDeleteImagePermission = permissions[2] as boolean; + this.hasScanImagePermission = permissions[3] as boolean; + }, error => this.errorHandler.error(error) ); + } // Trigger scan scanNow(t: Tag[]): void { if (t && t.length) { @@ -763,14 +785,6 @@ export class TagComponent implements OnInit, AfterViewInit { // pull command onCpError($event: any): void { - this.copyInput.setPullCommendShow(); - } - - public get developerRoleOrAbove(): boolean { - return this.memberRoleID === Roles.DEVELOPER || this.hasProjectAdminRole; - } - - public get guestRoleOrAbove(): boolean { - return this.memberRoleID === Roles.GUEST || this.memberRoleID === Roles.DEVELOPER || this.hasProjectAdminRole; + this.copyInput.setPullCommendShow(); } } diff --git a/src/portal/lib/src/utils.ts b/src/portal/lib/src/utils.ts index cbbd31231..b90d3984d 100644 --- a/src/portal/lib/src/utils.ts +++ b/src/portal/lib/src/utils.ts @@ -56,6 +56,14 @@ export const HTTP_GET_OPTIONS: RequestOptions = new RequestOptions({ "Pragma": 'no-cache' }) }); +export const HTTP_GET_OPTIONS_CACHE: RequestOptions = new RequestOptions({ + headers: new Headers({ + "Content-Type": 'application/json', + "Accept": 'application/json', + "Cache-Control": 'no-cache', + "Pragma": 'no-cache', + }) +}); export const FILE_UPLOAD_OPTION: RequestOptions = new RequestOptions({ headers: new Headers({ diff --git a/src/portal/lib/src/vulnerability-scanning/result-grid.component.html b/src/portal/lib/src/vulnerability-scanning/result-grid.component.html index 70e0e1c9a..cbc3c96bb 100644 --- a/src/portal/lib/src/vulnerability-scanning/result-grid.component.html +++ b/src/portal/lib/src/vulnerability-scanning/result-grid.component.html @@ -10,7 +10,7 @@
- + {{'VULNERABILITY.GRID.COLUMN_ID' | translate}} {{'VULNERABILITY.GRID.COLUMN_SEVERITY' | translate}} diff --git a/src/portal/lib/src/vulnerability-scanning/result-grid.component.spec.ts b/src/portal/lib/src/vulnerability-scanning/result-grid.component.spec.ts index 920644c39..3b3948660 100644 --- a/src/portal/lib/src/vulnerability-scanning/result-grid.component.spec.ts +++ b/src/portal/lib/src/vulnerability-scanning/result-grid.component.spec.ts @@ -8,6 +8,7 @@ import { ErrorHandler } from '../error-handler/index'; import { SharedModule } from '../shared/shared.module'; import { FilterComponent } from '../filter/index'; import {ChannelService} from "../channel/channel.service"; +import { UserPermissionService, UserPermissionDefaultService } from "../service/permission.service"; describe('ResultGridComponent (inline template)', () => { let component: ResultGridComponent; @@ -29,7 +30,8 @@ describe('ResultGridComponent (inline template)', () => { ErrorHandler, ChannelService, { provide: SERVICE_CONFIG, useValue: testConfig }, - { provide: ScanningResultService, useClass: ScanningResultDefaultService } + { provide: ScanningResultService, useClass: ScanningResultDefaultService }, + { provide: UserPermissionService, useClass: UserPermissionDefaultService } ] }); diff --git a/src/portal/lib/src/vulnerability-scanning/result-grid.component.ts b/src/portal/lib/src/vulnerability-scanning/result-grid.component.ts index 2eb1bf692..4283fdfb0 100644 --- a/src/portal/lib/src/vulnerability-scanning/result-grid.component.ts +++ b/src/portal/lib/src/vulnerability-scanning/result-grid.component.ts @@ -5,10 +5,12 @@ import { VulnerabilitySeverity } from '../service/index'; import { ErrorHandler } from '../error-handler/index'; +import { forkJoin } from "rxjs"; import { toPromise } from '../utils'; -import {ChannelService} from "../channel/channel.service"; - +import { ChannelService } from "../channel/channel.service"; +import { UserPermissionService } from "../service/permission.service"; +import { USERSTATICPERMISSION } from "../service/permission-static"; @Component({ selector: 'hbr-vulnerabilities-grid', templateUrl: './result-grid.component.html', @@ -20,12 +22,12 @@ export class ResultGridComponent implements OnInit { @Input() tagId: string; @Input() repositoryId: string; - @Input() withAdminRole: boolean; - + hasScanImagePermission: boolean; constructor( private scanningService: ScanningResultService, private channel: ChannelService, - private errorHandler: ErrorHandler + private userPermissionService: UserPermissionService, + private errorHandler: ErrorHandler, ) { } ngOnInit(): void { @@ -79,4 +81,14 @@ export class ResultGridComponent implements OnInit { scanNow(): void { this.channel.publishScanEvent(this.repositoryId + "/" + this.tagId); } + getScanPermissions(projectId: number): void { + + const hasScanImagePermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.KEY, USERSTATICPERMISSION.REPOSITORY_TAG_SCAN_JOB.VALUE.CREATE); + forkJoin(hasScanImagePermission).subscribe(permissions => { + this.hasScanImagePermission = permissions[0] as boolean; + }, error => { + this.errorHandler.error(error); + }); + } } diff --git a/src/portal/lib/src/vulnerability-scanning/result-tip.component.spec.ts b/src/portal/lib/src/vulnerability-scanning/result-tip.component.spec.ts index b1f41ab77..1c29e487e 100644 --- a/src/portal/lib/src/vulnerability-scanning/result-tip.component.spec.ts +++ b/src/portal/lib/src/vulnerability-scanning/result-tip.component.spec.ts @@ -6,6 +6,7 @@ import { SharedModule } from '../shared/shared.module'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { VULNERABILITY_SCAN_STATUS } from '../utils'; +import { UserPermissionService, UserPermissionDefaultService } from "../service/permission.service"; describe('ResultTipComponent (inline template)', () => { let component: ResultTipComponent; @@ -41,7 +42,8 @@ describe('ResultTipComponent (inline template)', () => { SharedModule ], declarations: [ResultTipComponent], - providers: [{ provide: SERVICE_CONFIG, useValue: testConfig }] + providers: [{ provide: SERVICE_CONFIG, useValue: testConfig }, + { provide: UserPermissionService, useClass: UserPermissionDefaultService }] }); })); diff --git a/src/portal/package-lock.json b/src/portal/package-lock.json index 39788b6ae..b0e9571a3 100644 --- a/src/portal/package-lock.json +++ b/src/portal/package-lock.json @@ -134,13 +134,15 @@ "version": "1.37.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", - "dev": true + "dev": true, + "optional": true }, "mime-types": { "version": "2.1.21", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "dev": true, + "optional": true, "requires": { "mime-db": "~1.37.0" } @@ -2757,9 +2759,9 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, "@types/jasmine": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.8.tgz", - "integrity": "sha512-OJSUxLaxXsjjhob2DBzqzgrkLmukM3+JMpRp0r0E4HTdT1nwDCWhaswjYxazPij6uOdzHCJfNbDjmQ1/rnNbCg==", + "version": "3.3.8", + "resolved": "http://registry.npm.taobao.org/@types/jasmine/download/@types/jasmine-3.3.8.tgz", + "integrity": "sha1-/Gq9kvcSFDFoXsmG8OyPd7Q5Cj4=", "dev": true }, "@types/jasminewd2": { @@ -6284,7 +6286,8 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "optional": true }, "aproba": { "version": "1.2.0", @@ -6305,12 +6308,14 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6325,17 +6330,20 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "optional": true }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -6452,7 +6460,8 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "optional": true }, "ini": { "version": "1.3.5", @@ -6464,6 +6473,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6478,6 +6488,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6485,12 +6496,14 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "optional": true }, "minipass": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -6509,6 +6522,7 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "optional": true, "requires": { "minimist": "0.0.8" } @@ -6589,7 +6603,8 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "optional": true }, "object-assign": { "version": "4.1.1", @@ -6601,6 +6616,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, "requires": { "wrappy": "1" } @@ -6686,7 +6702,8 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -6722,6 +6739,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6741,6 +6759,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6784,12 +6803,14 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true }, "yallist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", + "optional": true } } }, @@ -8703,10 +8724,9 @@ } }, "jasmine-core": { - "version": "2.99.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", - "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", - "dev": true + "version": "3.3.0", + "resolved": "http://registry.npm.taobao.org/jasmine-core/download/jasmine-core-3.3.0.tgz", + "integrity": "sha1-3qHNxjS8k8fg1K0nGF3zD6lxsQ4=" }, "jasmine-diff": { "version": "0.1.3", @@ -8965,7 +8985,7 @@ }, "karma-jasmine": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.2.tgz", + "resolved": "http://registry.npm.taobao.org/karma-jasmine/download/karma-jasmine-1.1.2.tgz", "integrity": "sha1-OU8rJf+0pkS5rabyLUQ+L9CIhsM=", "dev": true }, diff --git a/src/portal/package.json b/src/portal/package.json index 0111b1224..fc0b21174 100644 --- a/src/portal/package.json +++ b/src/portal/package.json @@ -42,6 +42,7 @@ "buffer": "^5.2.1", "core-js": "^2.5.4", "intl": "^1.2.5", + "jasmine-core": "^3.3.0", "jquery": "^3.3.1", "mutationobserver-shim": "^0.3.2", "ng-packagr": "^4.1.1", @@ -65,18 +66,17 @@ "@angular/compiler-cli": "^7.1.3", "@angular/language-service": "^7.1.3", "@types/core-js": "^0.9.41", - "@types/jasmine": "~2.8.6", + "@types/jasmine": "^3.3.1", "@types/jasminewd2": "~2.0.3", "@types/node": "~8.9.4", "codelyzer": "~4.2.1", "enhanced-resolve": "^3.0.0", - "jasmine-core": "~2.99.1", "jasmine-spec-reporter": "~4.2.1", "karma": "~1.7.1", "karma-chrome-launcher": "~2.2.0", "karma-cli": "^1.0.1", "karma-coverage-istanbul-reporter": "~2.0.0", - "karma-jasmine": "~1.1.1", + "karma-jasmine": "^1.1.2", "karma-jasmine-html-reporter": "^0.2.2", "karma-mocha-reporter": "^2.2.4", "karma-remap-istanbul": "^0.6.0", diff --git a/src/portal/src/app/config/config.component.html b/src/portal/src/app/config/config.component.html index 2fa843afc..4e0b6a34b 100644 --- a/src/portal/src/app/config/config.component.html +++ b/src/portal/src/app/config/config.component.html @@ -25,7 +25,10 @@ - + diff --git a/src/portal/src/app/project/list-chart-versions/list-chart-versions.component.html b/src/portal/src/app/project/list-chart-versions/list-chart-versions.component.html index b870f47af..6e5bbdcdf 100644 --- a/src/portal/src/app/project/list-chart-versions/list-chart-versions.component.html +++ b/src/portal/src/app/project/list-chart-versions/list-chart-versions.component.html @@ -10,8 +10,6 @@ [chartName]='chartName' [roleName]='roleName' [hasSignedIn]='hasSignedIn' - [projectRoleID]='project_member_role_id' - [hasProjectAdminRole]='hasProjectAdminRole' (versionClickEvt)='onVersionClick($event)' (backEvt)='gotoChartList()'> diff --git a/src/portal/src/app/project/list-chart-versions/list-chart-versions.component.ts b/src/portal/src/app/project/list-chart-versions/list-chart-versions.component.ts index a4315d97a..79d17c86e 100644 --- a/src/portal/src/app/project/list-chart-versions/list-chart-versions.component.ts +++ b/src/portal/src/app/project/list-chart-versions/list-chart-versions.component.ts @@ -21,9 +21,7 @@ export class ListChartVersionsComponent implements OnInit { roleName: string; hasSignedIn: boolean; - hasProjectAdminRole: boolean; currentUser: SessionUser; - project_member_role_id: number; constructor( private route: ActivatedRoute, @@ -39,10 +37,8 @@ export class ListChartVersionsComponent implements OnInit { let resolverData = this.route.snapshot.data; if (resolverData) { let project = (resolverData["projectResolver"]); - this.hasProjectAdminRole = project.has_project_admin_role; this.roleName = project.role_name; this.projectName = project.name; - this.project_member_role_id = project.current_user_role_id; } } diff --git a/src/portal/src/app/project/list-charts/list-charts.component.html b/src/portal/src/app/project/list-charts/list-charts.component.html index 9f6c59098..7f17ab609 100644 --- a/src/portal/src/app/project/list-charts/list-charts.component.html +++ b/src/portal/src/app/project/list-charts/list-charts.component.html @@ -4,6 +4,5 @@ [urlPrefix]='urlPrefix' [hasSignedIn]='hasSignedIn' [projectRoleID]='project_member_role_id' - [hasProjectAdminRole]='hasProjectAdminRole' (chartClickEvt)='onChartClick($event)'> diff --git a/src/portal/src/app/project/list-charts/list-charts.component.ts b/src/portal/src/app/project/list-charts/list-charts.component.ts index 3f8452a73..5bb48e2c8 100644 --- a/src/portal/src/app/project/list-charts/list-charts.component.ts +++ b/src/portal/src/app/project/list-charts/list-charts.component.ts @@ -17,7 +17,6 @@ export class ListChartsComponent implements OnInit { projectName: string; urlPrefix: string; hasSignedIn: boolean; - hasProjectAdminRole: boolean; project_member_role_id: number; currentUser: SessionUser; @@ -35,7 +34,6 @@ export class ListChartsComponent implements OnInit { if (resolverData) { let project = (resolverData["projectResolver"]); this.projectName = project.name; - this.hasProjectAdminRole = project.has_project_admin_role; this.project_member_role_id = project.current_user_role_id; } } diff --git a/src/portal/src/app/project/member/add-member/add-member.component.html b/src/portal/src/app/project/member/add-member/add-member.component.html index 3fc49c83d..25d6cf17e 100644 --- a/src/portal/src/app/project/member/add-member/add-member.component.html +++ b/src/portal/src/app/project/member/add-member/add-member.component.html @@ -32,6 +32,10 @@
+
+ + +
diff --git a/src/portal/src/app/project/member/member.component.html b/src/portal/src/app/project/member/member.component.html index f60bbb8ec..1b6c15255 100644 --- a/src/portal/src/app/project/member/member.component.html +++ b/src/portal/src/app/project/member/member.component.html @@ -13,21 +13,22 @@
- - {{'MEMBER.ACTION' | translate}} - - - + + + + - + diff --git a/src/portal/src/app/project/member/member.component.ts b/src/portal/src/app/project/member/member.component.ts index f2f141a38..85eb41682 100644 --- a/src/portal/src/app/project/member/member.component.ts +++ b/src/portal/src/app/project/member/member.component.ts @@ -1,5 +1,5 @@ -import {finalize} from 'rxjs/operators'; +import { finalize } from 'rxjs/operators'; // Copyright (c) 2017 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,9 +15,9 @@ import {finalize} from 'rxjs/operators'; // limitations under the License. import { Component, OnInit, ViewChild, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { Subscription } from "rxjs"; -import {TranslateService} from "@ngx-translate/core"; -import {operateChanges, OperateInfo, OperationService, OperationState} from "@harbor/ui"; +import { Subscription, forkJoin } from "rxjs"; +import { TranslateService } from "@ngx-translate/core"; +import { operateChanges, OperateInfo, OperationService, OperationState } from "@harbor/ui"; import { MessageHandlerService } from "../../shared/message-handler/message-handler.service"; import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from "../../shared/shared.const"; @@ -31,7 +31,8 @@ import { SessionUser } from "../../shared/session-user"; import { AddGroupComponent } from './add-group/add-group.component'; import { MemberService } from "./member.service"; import { AddMemberComponent } from "./add-member/add-member.component"; -import {AppConfigService} from "../../app-config.service"; +import { AppConfigService } from "../../app-config.service"; +import { UserPermissionService, USERSTATICPERMISSION, ErrorHandler } from "@harbor/ui"; @Component({ templateUrl: "member.component.html", @@ -46,7 +47,6 @@ export class MemberComponent implements OnInit, OnDestroy { delSub: Subscription; currentUser: SessionUser; - hasProjectAdminRole: boolean; batchOps = 'delete'; searchMember: string; @@ -65,7 +65,9 @@ export class MemberComponent implements OnInit, OnDestroy { @ViewChild(AddGroupComponent) addGroupComponent: AddGroupComponent; - + hasCreateMemberPermission: boolean; + hasUpdateMemberPermission: boolean; + hasDeleteMemberPermission: boolean; constructor( private route: ActivatedRoute, private router: Router, @@ -76,6 +78,8 @@ export class MemberComponent implements OnInit, OnDestroy { private session: SessionService, private operationService: OperationService, private appConfigService: AppConfigService, + private userPermissionService: UserPermissionService, + private errorHandler: ErrorHandler, private ref: ChangeDetectorRef) { this.delSub = OperateDialogService.confirmationConfirm$.subscribe(message => { @@ -102,14 +106,12 @@ export class MemberComponent implements OnInit, OnDestroy { this.projectId = +this.route.snapshot.parent.params["id"]; // Get current user from registered resolver. this.currentUser = this.session.getCurrentUser(); - let resolverData = this.route.snapshot.parent.data; - if (resolverData) { - this.hasProjectAdminRole = (resolverData["projectResolver"]).has_project_admin_role; - } this.retrieve(this.projectId, ""); if (this.appConfigService.isLdapMode()) { this.isLdapMode = true; } + // get member permission rule + this.getMemberPermissionRule(this.projectId); } doSearch(searchMember: string) { @@ -126,23 +128,23 @@ export class MemberComponent implements OnInit, OnDestroy { this.selectedRow = []; this.memberService .listMembers(projectId, username).pipe( - finalize(() => this.loading = false)) + finalize(() => this.loading = false)) .subscribe( - response => { - this.members = response; - let hnd = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => clearInterval(hnd), 1000); - }, - error => { - this.router.navigate(["/harbor", "projects"]); - this.messageHandlerService.handleError(error); - }); + response => { + this.members = response; + let hnd = setInterval(() => this.ref.markForCheck(), 100); + setTimeout(() => clearInterval(hnd), 1000); + }, + error => { + this.router.navigate(["/harbor", "projects"]); + this.messageHandlerService.handleError(error); + }); } get onlySelf(): boolean { if (this.selectedRow.length === 1 && this.selectedRow[0].entity_type === 'u' && - this.selectedRow[0].entity_id === this.currentUser.user_id) { + this.selectedRow[0].entity_id === this.currentUser.user_id) { return true; } return false; @@ -173,7 +175,7 @@ export class MemberComponent implements OnInit, OnDestroy { addedGroup(result: boolean) { this.searchMember = ""; this.retrieve(this.projectId, ""); - } + } changeMembersRole(members: Member[], roleId: number) { if (!members) { @@ -182,9 +184,9 @@ export class MemberComponent implements OnInit, OnDestroy { let changeOperate = (projectId: number, member: Member, ) => { return this.memberService - .changeMemberRole(projectId, member.id, roleId) - .then( () => this.batchChangeRoleInfos[member.id] = 'done') - .catch(error => this.messageHandlerService.handleError(error + ": " + member.entity_name)); + .changeMemberRole(projectId, member.id, roleId) + .then(() => this.batchChangeRoleInfos[member.id] = 'done') + .catch(error => this.messageHandlerService.handleError(error + ": " + member.entity_name)); }; // Preparation for members role change @@ -223,7 +225,7 @@ export class MemberComponent implements OnInit, OnDestroy { ConfirmationTargets.PROJECT_MEMBER, ConfirmationButtons.DELETE_CANCEL ); - this.OperateDialogService.openComfirmDialog(deletionMessage); + this.OperateDialogService.openComfirmDialog(deletionMessage); } } @@ -250,15 +252,15 @@ export class MemberComponent implements OnInit, OnDestroy { return this.memberService .deleteMember(projectId, member.id) .then(response => { - this.translate.get("BATCH.DELETED_SUCCESS").subscribe(res => { - operateChanges(operMessage, OperationState.success); - }); - }) - .catch(error => { - this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => { - operateChanges(operMessage, OperationState.failure, res); - }); + this.translate.get("BATCH.DELETED_SUCCESS").subscribe(res => { + operateChanges(operMessage, OperationState.success); }); + }) + .catch(error => { + this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => { + operateChanges(operMessage, OperationState.failure, res); + }); + }); }; // Deleting member then wating for results @@ -270,4 +272,17 @@ export class MemberComponent implements OnInit, OnDestroy { this.retrieve(this.projectId, ""); }); } + getMemberPermissionRule(projectId: number): void { + let hasCreateMemberPermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.MEMBER.KEY, USERSTATICPERMISSION.MEMBER.VALUE.CREATE); + let hasUpdateMemberPermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.MEMBER.KEY, USERSTATICPERMISSION.MEMBER.VALUE.UPDATE); + let hasDeleteMemberPermission = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.MEMBER.KEY, USERSTATICPERMISSION.MEMBER.VALUE.DELETE); + forkJoin(hasCreateMemberPermission, hasUpdateMemberPermission, hasDeleteMemberPermission).subscribe(MemberRule => { + this.hasCreateMemberPermission = MemberRule[0] as boolean; + this.hasUpdateMemberPermission = MemberRule[1] as boolean; + this.hasDeleteMemberPermission = MemberRule[2] as boolean; + }, error => this.errorHandler.error(error)); + } } diff --git a/src/portal/src/app/project/project-config/project-config.component.html b/src/portal/src/app/project/project-config/project-config.component.html index d51542b48..bbfbc4254 100644 --- a/src/portal/src/app/project/project-config/project-config.component.html +++ b/src/portal/src/app/project/project-config/project-config.component.html @@ -1,5 +1,5 @@
- +
\ No newline at end of file diff --git a/src/portal/src/app/project/project-config/project-config.component.ts b/src/portal/src/app/project/project-config/project-config.component.ts index d12a85057..5499aaf40 100644 --- a/src/portal/src/app/project/project-config/project-config.component.ts +++ b/src/portal/src/app/project/project-config/project-config.component.ts @@ -28,7 +28,6 @@ export class ProjectConfigComponent implements OnInit { projectName: string; currentUser: SessionUser; hasSignedIn: boolean; - hasProjectAdminRole: boolean; constructor( private route: ActivatedRoute, @@ -42,7 +41,6 @@ export class ProjectConfigComponent implements OnInit { let resolverData = this.route.snapshot.parent.data; if (resolverData) { let pro: Project = resolverData['projectResolver']; - this.hasProjectAdminRole = pro.has_project_admin_role; this.projectName = pro.name; } } diff --git a/src/portal/src/app/project/project-detail/project-detail.component.html b/src/portal/src/app/project/project-detail/project-detail.component.html index 7b43be2ca..5d3ea84c8 100644 --- a/src/portal/src/app/project/project-detail/project-detail.component.html +++ b/src/portal/src/app/project/project-detail/project-detail.component.html @@ -4,28 +4,28 @@

{{currentProject.name}} {{roleName | translate}}

+ + \ No newline at end of file diff --git a/src/portal/src/app/project/robot-account/robot-account.component.ts b/src/portal/src/app/project/robot-account/robot-account.component.ts index 006a59d81..d0f2d0cac 100644 --- a/src/portal/src/app/project/robot-account/robot-account.component.ts +++ b/src/portal/src/app/project/robot-account/robot-account.component.ts @@ -25,7 +25,10 @@ import { operateChanges, OperateInfo, OperationService, - OperationState + OperationState, + UserPermissionService, + USERSTATICPERMISSION, + ErrorHandler } from "@harbor/ui"; @Component({ @@ -48,12 +51,17 @@ export class RobotAccountComponent implements OnInit, OnDestroy { robots: Robot[]; projectId: number; subscription: Subscription; + hasRobotCreatePermission: boolean; + hasRobotUpdatePermission: boolean; + hasRobotDeletePermission: boolean; constructor( private route: ActivatedRoute, private robotService: RobotService, private OperateDialogService: ConfirmationDialogService, private operationService: OperationService, private translate: TranslateService, + private userPermissionService: UserPermissionService, + private errorHandler: ErrorHandler, private ref: ChangeDetectorRef, private messageHandlerService: MessageHandlerService ) { @@ -80,8 +88,24 @@ export class RobotAccountComponent implements OnInit, OnDestroy { } this.searchRobot = ""; this.retrieve(); + this.getPermissionsList(this.projectId); } + getPermissionsList(projectId: number): void { + let permissionsList = []; + permissionsList.push(this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.ROBOT.KEY, USERSTATICPERMISSION.ROBOT.VALUE.CREATE)); + permissionsList.push(this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.ROBOT.KEY, USERSTATICPERMISSION.ROBOT.VALUE.UPDATE)); + permissionsList.push(this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.ROBOT.KEY, USERSTATICPERMISSION.ROBOT.VALUE.DELETE)); + forkJoin(...permissionsList).subscribe(Rules => { + this.hasRobotCreatePermission = Rules[0] as boolean; + this.hasRobotUpdatePermission = Rules[1] as boolean; + this.hasRobotDeletePermission = Rules[2] as boolean; + + }, error => this.errorHandler.error(error)); + } ngOnDestroy(): void { if (this.subscription) { this.subscription.unsubscribe(); @@ -122,7 +146,7 @@ export class RobotAccountComponent implements OnInit, OnDestroy { this.selectedRow = []; }) ) - .subscribe(() => {}); + .subscribe(() => { }); } delOperate(robot: Robot) { diff --git a/src/portal/src/app/replication/replication-page.component.html b/src/portal/src/app/replication/replication-page.component.html index 579d1d5b1..e7efd3ce4 100644 --- a/src/portal/src/app/replication/replication-page.component.html +++ b/src/portal/src/app/replication/replication-page.component.html @@ -1,3 +1,12 @@
- +
\ No newline at end of file diff --git a/src/portal/src/app/replication/replication-page.component.ts b/src/portal/src/app/replication/replication-page.component.ts index b43aa878e..2995ba9dd 100644 --- a/src/portal/src/app/replication/replication-page.component.ts +++ b/src/portal/src/app/replication/replication-page.component.ts @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core'; -import {ActivatedRoute, Router} from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; -import { ReplicationComponent } from '@harbor/ui'; - -import {SessionService} from "../shared/session.service"; -import {Project} from "../project/project"; -import {ProjectService} from "../project/project.service"; +import { SessionService } from "../shared/session.service"; +import { Project } from "../project/project"; +import { ProjectService } from "../project/project.service"; +import { ReplicationComponent, UserPermissionService, USERSTATICPERMISSION, ErrorHandler } from "@harbor/ui"; +import { forkJoin } from 'rxjs'; @Component({ selector: 'replication', @@ -28,25 +28,30 @@ export class ReplicationPageComponent implements OnInit, AfterViewInit { projectIdentify: string | number; @ViewChild("replicationView") replicationView: ReplicationComponent; projectName: string; - + hasCreateReplicationPermission: boolean; + hasUpdateReplicationPermission: boolean; + hasDeleteReplicationPermission: boolean; + hasExecuteReplicationPermission: boolean; constructor(private route: ActivatedRoute, - private router: Router, - private proService: ProjectService, - private session: SessionService) { } + private router: Router, + private proService: ProjectService, + private userPermissionService: UserPermissionService, + private errorHandler: ErrorHandler, + private session: SessionService) { } ngOnInit(): void { this.projectIdentify = +this.route.snapshot.parent.params['id']; - + this.getReplicationPermissions(this.projectIdentify); this.proService.listProjects("", undefined).toPromise() - .then(response => { - let projects = response.json() as Project[]; - if (projects.length) { - let project = projects.find(data => data.project_id === this.projectIdentify); - if (project) { - this.projectName = project.name; - } + .then(response => { + let projects = response.json() as Project[]; + if (projects.length) { + let project = projects.find(data => data.project_id === this.projectIdentify); + if (project) { + this.projectName = project.name; } - }); + } + }); } public get isSystemAdmin(): boolean { @@ -66,4 +71,24 @@ export class ReplicationPageComponent implements OnInit, AfterViewInit { goRegistry(): void { this.router.navigate(['/harbor', 'registries']); } + + getReplicationPermissions(projectId: number): void { + + let permissionsCreate = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.REPLICATION.KEY, USERSTATICPERMISSION.REPLICATION.VALUE.CREATE); + let permissionsUpdate = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.REPLICATION.KEY, USERSTATICPERMISSION.REPLICATION.VALUE.UPDATE); + let permissionsDelete = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.REPLICATION.KEY, USERSTATICPERMISSION.REPLICATION.VALUE.DELETE); + let permissionsExecute = this.userPermissionService.getPermission(projectId, + USERSTATICPERMISSION.REPLICATION_JOB.KEY, USERSTATICPERMISSION.REPLICATION_JOB.VALUE.CREATE); + forkJoin(permissionsCreate, permissionsUpdate, permissionsDelete, permissionsExecute).subscribe(permissions => { + this.hasCreateReplicationPermission = permissions[0] as boolean; + this.hasUpdateReplicationPermission = permissions[1] as boolean; + this.hasDeleteReplicationPermission = permissions[2] as boolean; + this.hasExecuteReplicationPermission = permissions[3] as boolean; + }, error => { + this.errorHandler.error(error); + }); + } } diff --git a/src/portal/src/app/replication/total-replication/total-replication-page.component.html b/src/portal/src/app/replication/total-replication/total-replication-page.component.html index 13be7aad5..a823aac15 100644 --- a/src/portal/src/app/replication/total-replication/total-replication-page.component.html +++ b/src/portal/src/app/replication/total-replication/total-replication-page.component.html @@ -1,4 +1,13 @@

{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}

- +
\ No newline at end of file diff --git a/src/portal/src/app/repository/tag-detail/tag-detail-page.component.html b/src/portal/src/app/repository/tag-detail/tag-detail-page.component.html index ca6f72401..6eb680c31 100644 --- a/src/portal/src/app/repository/tag-detail/tag-detail-page.component.html +++ b/src/portal/src/app/repository/tag-detail/tag-detail-page.component.html @@ -5,9 +5,9 @@ < {{repositoryId}} + [tagId]="tagId" + [withClair]="withClair" + [withAdmiral]="withAdmiral" + [projectId]="projectId" + [repositoryId]="repositoryId"> \ No newline at end of file diff --git a/src/portal/src/app/repository/tag-detail/tag-detail-page.component.ts b/src/portal/src/app/repository/tag-detail/tag-detail-page.component.ts index 9232789e6..f4c1d1f34 100644 --- a/src/portal/src/app/repository/tag-detail/tag-detail-page.component.ts +++ b/src/portal/src/app/repository/tag-detail/tag-detail-page.component.ts @@ -48,10 +48,6 @@ export class TagDetailPageComponent implements OnInit { return this.appConfigService.getConfig().with_clair; } - get withAdminRole(): boolean { - return this.session.getCurrentUser().has_admin_role; - } - goBack(tag: string): void { this.router.navigate(["harbor", "projects", this.projectId, "repositories", tag]); } diff --git a/src/portal/src/app/shared/message-handler/message-handler.service.ts b/src/portal/src/app/shared/message-handler/message-handler.service.ts index ed19f0875..efbc12642 100644 --- a/src/portal/src/app/shared/message-handler/message-handler.service.ts +++ b/src/portal/src/app/shared/message-handler/message-handler.service.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; -import { ErrorHandler } from '@harbor/ui'; +import { ErrorHandler, UserPermissionService } from '@harbor/ui'; import { MessageService } from '../../global-message/message.service'; import { AlertType, httpStatusCode } from '../../shared/shared.const'; @@ -27,6 +27,7 @@ export class MessageHandlerService implements ErrorHandler { constructor( private msgService: MessageService, private translate: TranslateService, + private userPermissionService: UserPermissionService, private session: SessionService) { } // Handle the error and map it to the suitable message @@ -46,6 +47,7 @@ export class MessageHandlerService implements ErrorHandler { this.msgService.announceAppLevelMessage(code, msg, AlertType.DANGER); // Session is invalid now, clare session cache this.session.clear(); + this.userPermissionService.clearPermissionCache(); } else { this.msgService.announceMessage(code, msg, AlertType.DANGER); } diff --git a/src/portal/src/app/shared/route/member-guard-activate.service.ts b/src/portal/src/app/shared/route/member-guard-activate.service.ts index 9ff813ea6..05b907dce 100644 --- a/src/portal/src/app/shared/route/member-guard-activate.service.ts +++ b/src/portal/src/app/shared/route/member-guard-activate.service.ts @@ -57,7 +57,7 @@ export class MemberGuard implements CanActivate, CanActivateChild { () => { // Add exception for repository in project detail router activation. this.projectService.getProject(projectId).subscribe(project => { - if (project.public === 1) { + if (project.metadata && project.metadata.public === 'true') { return resolve(true); } this.router.navigate([CommonRoutes.HARBOR_DEFAULT]); diff --git a/src/portal/src/app/shared/route/sign-in-guard-activate.service.ts b/src/portal/src/app/shared/route/sign-in-guard-activate.service.ts index 2cd39c961..4f6a72133 100644 --- a/src/portal/src/app/shared/route/sign-in-guard-activate.service.ts +++ b/src/portal/src/app/shared/route/sign-in-guard-activate.service.ts @@ -20,10 +20,11 @@ import { } from '@angular/router'; import { SessionService } from '../../shared/session.service'; import { CommonRoutes } from '../../shared/shared.const'; +import { UserPermissionService } from "@harbor/ui"; @Injectable() export class SignInGuard implements CanActivate, CanActivateChild { - constructor(private authService: SessionService, private router: Router) { } + constructor(private authService: SessionService, private router: Router, private userPermission: UserPermissionService) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise | boolean { // If user has logged in, should not login again @@ -34,6 +35,8 @@ export class SignInGuard implements CanActivate, CanActivateChild { this.authService.signOff() .then(() => { this.authService.clear(); // Destroy session cache + this.userPermission.clearPermissionCache(); + return resolve(true); }) .catch(error => { diff --git a/src/portal/src/app/shared/session.service.ts b/src/portal/src/app/shared/session.service.ts index c74e7e5b8..e03b8c8d0 100644 --- a/src/portal/src/app/shared/session.service.ts +++ b/src/portal/src/app/shared/session.service.ts @@ -20,7 +20,7 @@ import { Member } from '../project/member/member'; import { SignInCredential } from './sign-in-credential'; import { enLang } from '../shared/shared.const'; -import {HTTP_FORM_OPTIONS, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from "./shared.utils"; +import { HTTP_FORM_OPTIONS, HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from "./shared.utils"; const signInUrl = '/c/login'; const currentUserEndpoint = "/api/users/current"; @@ -67,7 +67,7 @@ export class SessionService { signIn(signInCredential: SignInCredential): Promise { // Build the form package let queryParam: string = 'principal=' + encodeURIComponent(signInCredential.principal) + - '&password=' + encodeURIComponent(signInCredential.password); + '&password=' + encodeURIComponent(signInCredential.password); // Trigger Http return this.http.post(signInUrl, queryParam, HTTP_FORM_OPTIONS) @@ -144,9 +144,9 @@ export class SessionService { return Promise.reject("Invalid account settings"); } return this.http.post(renameAdminEndpoint, JSON.stringify({}), HTTP_JSON_OPTIONS) - .toPromise() - .then(() => null) - .catch(error => this.handleError(error)); + .toPromise() + .then(() => null) + .catch(error => this.handleError(error)); } /** diff --git a/src/portal/src/app/shared/shared.const.ts b/src/portal/src/app/shared/shared.const.ts index 7cefb9dec..1bc2ddcf2 100644 --- a/src/portal/src/app/shared/shared.const.ts +++ b/src/portal/src/app/shared/shared.const.ts @@ -78,16 +78,19 @@ export const enum ConfirmationButtons { } export const ProjectTypes = { 0: 'PROJECT.ALL_PROJECTS', 1: 'PROJECT.PRIVATE_PROJECTS', 2: 'PROJECT.PUBLIC_PROJECTS' }; -export const RoleInfo = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' }; -export const RoleMapping = { 'projectAdmin': 'MEMBER.PROJECT_ADMIN', 'developer': 'MEMBER.DEVELOPER', 'guest': 'MEMBER.GUEST' }; +export const RoleInfo = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST', 4: 'MEMBER.PROJECT_MASTER' }; +export const RoleMapping = { 'projectAdmin': 'MEMBER.PROJECT_ADMIN', +'master': 'MEMBER.PROJECT_MASTER', 'developer': 'MEMBER.DEVELOPER', 'guest': 'MEMBER.GUEST' }; export const ProjectRoles = [ { id: 1, value: "MEMBER.PROJECT_ADMIN" }, { id: 2, value: "MEMBER.DEVELOPER" }, - { id: 3, value: "MEMBER.GUEST" } + { id: 3, value: "MEMBER.GUEST" }, + { id: 4, value: "MEMBER.PROJECT_MASTER" }, ]; export enum Roles { PROJECT_ADMIN = 1, + PROJECT_MASTER = 4, DEVELOPER = 2, GUEST = 3, OTHER = 0, diff --git a/src/portal/src/app/user/user.component.ts b/src/portal/src/app/user/user.component.ts index 5123ac382..3a3e6e299 100644 --- a/src/portal/src/app/user/user.component.ts +++ b/src/portal/src/app/user/user.component.ts @@ -26,8 +26,8 @@ import { AppConfigService } from '../app-config.service'; import { NewUserModalComponent } from './new-user-modal.component'; import { UserService } from './user.service'; import { User } from './user'; -import {ChangePasswordComponent} from "./change-password/change-password.component"; -import {operateChanges, OperateInfo, OperationService, OperationState} from "@harbor/ui"; +import { ChangePasswordComponent } from "./change-password/change-password.component"; +import { operateChanges, OperateInfo, OperationService, OperationState } from "@harbor/ui"; /** * NOTES: * Pagination for this component is a temporary workaround solution. It will be replaced in future release. @@ -153,7 +153,7 @@ export class UserComponent implements OnInit, OnDestroy { return this.onGoing; } - ngOnInit(): void {} + ngOnInit(): void { } ngOnDestroy(): void { if (this.deletionSubscription) { @@ -223,15 +223,15 @@ export class UserComponent implements OnInit, OnDestroy { } } - Promise.all(promiseLists).then(() => { - this.selectedRow = []; - this.refresh(); - }) + Promise.all(promiseLists).then(() => { + this.selectedRow = []; + this.refresh(); + }) .catch(error => { - this.selectedRow = []; - this.msgHandler.handleError(error); - }); - } + this.selectedRow = []; + this.msgHandler.handleError(error); + }); + } } // Delete the specified user @@ -298,7 +298,7 @@ export class UserComponent implements OnInit, OnDestroy { this.translate.get('BATCH.DELETED_FAILURE').subscribe(res => { operateChanges(operMessage, OperationState.failure, res); }); - }); + }); } // Refresh the user list @@ -310,15 +310,15 @@ export class UserComponent implements OnInit, OnDestroy { this.originalUsers = this.userService.getUsers(); this.originalUsers.then(users => { - this.onGoing = false; + this.onGoing = false; - this.totalCount = users.length; - this.users = users.slice(from, to); // First page + this.totalCount = users.length; + this.users = users.slice(from, to); // First page - this.forceRefreshView(5000); + this.forceRefreshView(5000); - return users; - }) + return users; + }) .catch(error => { this.onGoing = false; this.msgHandler.handleError(error); diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 5ea73b524..ebdeff096 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -219,6 +219,7 @@ "ROLE": "Role", "SYS_ADMIN": "System Admin", "PROJECT_ADMIN": "Project Admin", + "PROJECT_MASTER": "Master", "DEVELOPER": "Developer", "GUEST": "Guest", "DELETE": "Delete", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index b286dd804..181433c6e 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -219,6 +219,7 @@ "ROLE": "Rol", "SYS_ADMIN": "Administrador del sistema", "PROJECT_ADMIN": "Administrador del proyecto", + "PROJECT_MASTER": "Mantenedor", "DEVELOPER": "Desarrollador", "GUEST": "Invitado", "DELETE": "Eliminar", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index 13c03ec0b..af362a39c 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -224,6 +224,7 @@ "USER_TYPE": "User", "SYS_ADMIN": "System Admin", "PROJECT_ADMIN": "Project Admin", + "PROJECT_MASTER": "préposé à la maintenance", "DEVELOPER": "Développeur", "GUEST": "Invité", "DELETE": "Supprimer", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 7cd47fef4..684cff475 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -217,6 +217,7 @@ "ROLE": "Função", "SYS_ADMIN": "Administrador do Sistema", "PROJECT_ADMIN": "Administrador do Projeto", + "PROJECT_MASTER": "Mantenedor", "DEVELOPER": "Desenvolvedor", "GUEST": "Visitante", "DELETE": "Remover", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 8766b43c4..a4475b4dc 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -219,6 +219,7 @@ "ROLE": "角色", "SYS_ADMIN": "系统管理员", "PROJECT_ADMIN": "项目管理员", + "PROJECT_MASTER": "维护人员", "DEVELOPER": "开发人员", "GUEST": "访客", "DELETE": "删除", diff --git a/tests/resources/Harbor-Pages/Project-Config.robot b/tests/resources/Harbor-Pages/Project-Config.robot index 045238386..4d5467a7b 100644 --- a/tests/resources/Harbor-Pages/Project-Config.robot +++ b/tests/resources/Harbor-Pages/Project-Config.robot @@ -8,6 +8,7 @@ ${HARBOR_VERSION} V1.1.1 *** Keywords *** Goto Project Config + Sleep 3 Click Element //project-detail//ul/li[contains(.,'Configuration')] Sleep 2 diff --git a/tests/resources/Harbor-Pages/Project.robot b/tests/resources/Harbor-Pages/Project.robot index 9b60966f8..2a8f79799 100644 --- a/tests/resources/Harbor-Pages/Project.robot +++ b/tests/resources/Harbor-Pages/Project.robot @@ -52,6 +52,7 @@ Go To Project Log Sleep 2 Switch To Member + Sleep 3 Click Element xpath=${project_member_xpath} Sleep 1 @@ -89,22 +90,22 @@ Search Private Projects Make Project Private [Arguments] ${projectname} Go Into Project ${project name} - Sleep 1 + Sleep 2 Click Element xpath=//project-detail//a[contains(.,'Configuration')] Sleep 1 Checkbox Should Be Selected xpath=//input[@name='public'] - Click Element //div[@id='clr-wrapper-public']//label + Click Element //div[@id="clr-wrapper-public"]//label[1] Wait Until Element Is Enabled //button[contains(.,'SAVE')] Click Element //button[contains(.,'SAVE')] Wait Until Page Contains Configuration has been successfully saved Make Project Public [Arguments] ${projectname} - Go Into Project ${project name} - Sleep 1 + Go Into Project ${project name} + Sleep 2 Click Element xpath=//project-detail//a[contains(.,'Configuration')] Checkbox Should Not Be Selected xpath=//input[@name='public'] - Click Element //div[@id='clr-wrapper-public']//label + Click Element //div[@id="clr-wrapper-public"]//label[1] Wait Until Element Is Enabled //button[contains(.,'SAVE')] Click Element //button[contains(.,'SAVE')] Wait Until Page Contains Configuration has been successfully saved diff --git a/tests/resources/Harbor-Pages/ToolKit_Elements.robot b/tests/resources/Harbor-Pages/ToolKit_Elements.robot index 17d2ceb13..27db34257 100644 --- a/tests/resources/Harbor-Pages/ToolKit_Elements.robot +++ b/tests/resources/Harbor-Pages/ToolKit_Elements.robot @@ -16,5 +16,5 @@ Documentation This resource provides any keywords related to the Harbor private registry appliance *** Variables *** -${member_action_xpath} //*[@id='member-action'] -${delete_action_xpath} //clr-dropdown/clr-dropdown-menu/button[4] +${member_action_xpath} //*[@id="member-action"] +${delete_action_xpath} //clr-dropdown/clr-dropdown-menu/button[5] diff --git a/tests/resources/Harbor-Pages/Vulnerability.robot b/tests/resources/Harbor-Pages/Vulnerability.robot index 98618b824..0e377229b 100644 --- a/tests/resources/Harbor-Pages/Vulnerability.robot +++ b/tests/resources/Harbor-Pages/Vulnerability.robot @@ -63,6 +63,7 @@ Enable Scan On Push Sleep 10 Vulnerability Not Ready Project Hint + Sleep 2 ${element}= Set Variable xpath=//span[contains(@class, 'db-status-warning')] Wait Until Element Is Visible And Enabled ${element}