Merge pull request #6758 from jwangyangls/privilege_escalation

user permission change and add master role
This commit is contained in:
jwangyangls 2019-01-31 18:57:22 +08:00 committed by GitHub
commit 3d726219e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 1128 additions and 554 deletions

View File

@ -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

View File

@ -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
]

View File

@ -23,14 +23,14 @@
<div *ngIf="!isCardView" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-datagrid (clrDgRefresh)="refresh()" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRows">
<clr-dg-action-bar>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!developerRoleOrAbove" (click)="onChartUpload()">
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!hasUploadHelmChartsPermission" (click)="onChartUpload()">
<clr-icon shape="upload" size="16"></clr-icon>{{'HELM_CHART.UPLOAD' | translate}}
</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!hasProjectAdminRole || selectedRows.length<1"
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!hasDeleteHelmChartsPermission || selectedRows.length<1"
(click)="openChartDeleteModal(selectedRows)">
<clr-icon shape="trash" size="16"></clr-icon>{{'BUTTON.DELETE' | translate}}
</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="selectedRows.length!==1" (click)="downloadLatestVersion()">
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!hasDownloadHelmChartsPermission ||selectedRows.length!==1" (click)="downloadLatestVersion()">
<clr-icon shape="download" size="16"></clr-icon>{{'HELM_CHART.DOWNLOAD' | translate}}
</button>
</clr-dg-action-bar>

View File

@ -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<any>();
@Output() chartDownloadEve = new EventEmitter<string>();
@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<SystemInfo>(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) {

View File

@ -38,18 +38,18 @@
<clr-datagrid (clrDgRefresh)="refresh()" [clrDgLoading]="loading" [(clrDgSelected)]="selectedRows">
<clr-dg-action-bar>
<button type="button" class="btn btn-sm btn-secondary"
[disabled]="!(selectedRows.length===1)"
[disabled]="!(selectedRows.length===1) || !hasDownloadHelmChartVersionPermission"
(click)="versionDownload()">
<clr-icon shape="download" size="16"></clr-icon>&nbsp;{{'HELM_CHART.DOWNLOAD' | translate}}
</button>
<button type="button" class="btn btn-sm btn-secondary"
[disabled]="selectedRows.length<=0 || !hasProjectAdminRole"
[disabled]="selectedRows.length<=0 || !hasDeleteHelmChartVersionPermission"
(click)="openVersionDeleteModal(selectedRows)">
<clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'BUTTON.DELETE' | translate}}
</button>
<clr-dropdown>
<button type="button" class="btn btn-sm btn-secondary" clrDropdownTrigger
[disabled]="!(selectedRows.length===1 && developerRoleOrAbove)">
<button type="button" class="btn btn-sm btn-secondary" clrDropdownTrigger
[disabled]="!(selectedRows.length===1)|| !hasAddRemoveHelmChartVersionPermission">
<clr-icon shape="plus" size="16"></clr-icon>{{'REPOSITORY.ADD_LABELS' | translate}}
</button>
<clr-dropdown-menu clrPosition="bottom-left" *clrIfOpen>
@ -144,7 +144,7 @@
<button type="button" class="btn btn-link"
(click)="versionDownload($event, item)">{{'HELM_CHART.DOWNLOAD' | translate}}</button>
<button type="button" class="btn btn-link"
[disabled]="selectedRows.length<=0 || !hasProjectAdminRole"
[disabled]="selectedRows.length<=0 || !hasDeleteHelmChartVersionPermission"
(click)="deleteVersionCard($event, item)">{{'BUTTON.DELETE' | translate}}</button>
</clr-dropdown>
</div>

View File

@ -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<string>();
@Output() backEvt = new EventEmitter<any>();
@ -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));
}
}

View File

@ -11,9 +11,9 @@
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 btnGroup">
<button type="button" class="btn btn-sm btn-secondary" (click)="openModal()"><clr-icon shape="plus" size="16"></clr-icon>&nbsp;{{'LABEL.NEW_LABEL' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length == 1)" (click)="editLabel(selectedRow)"><clr-icon shape="pencil" size="16"></clr-icon>&nbsp;{{'LABEL.EDIT' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow.length" (click)="deleteLabels(selectedRow)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'LABEL.DELETE' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!hasCreateLabelPermission" (click)="openModal()"><clr-icon shape="plus" size="16"></clr-icon>&nbsp;{{'LABEL.NEW_LABEL' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length == 1) || !hasUpdateLabelPermission" (click)="editLabel(selectedRow)"><clr-icon shape="pencil" size="16"></clr-icon>&nbsp;{{'LABEL.EDIT' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!selectedRow.length || !hasDeleteLabelPermission" (click)="deleteLabels(selectedRow)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'LABEL.DELETE' | translate}}</button>
<hbr-create-edit-label [scope]="scope" [projectId]="projectId" (reload)="reload()"></hbr-create-edit-label>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 content-mt">

View File

@ -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);
}
}

View File

@ -1,10 +1,10 @@
<div class="list-rule">
<clr-datagrid [clrDgLoading]="loading" [(clrDgSingleSelected)]="selectedRow" (clrDgSingleSelectedChange)="selectRule($event)" [clrDgRowSelection]="true">
<clr-dg-action-bar>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" (click)="openModal()"><clr-icon shape="plus" size="16"></clr-icon>&nbsp;{{'REPLICATION.NEW_REPLICATION_RULE' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" [disabled]="!selectedRow" (click)="editRule(selectedRow)"><clr-icon shape="pencil" size="16"></clr-icon>&nbsp;{{'REPLICATION.EDIT_POLICY' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" [disabled]="!selectedRow" (click)="deleteRule(selectedRow)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPLICATION.DELETE_POLICY' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="isSystemAdmin" [disabled]="!selectedRow" (click)="replicateRule(selectedRow)"><clr-icon shape="export" size="16"></clr-icon>&nbsp;{{'REPLICATION.REPLICATE' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasCreateReplicationPermission" (click)="openModal()"><clr-icon shape="plus" size="16"></clr-icon>&nbsp;{{'REPLICATION.NEW_REPLICATION_RULE' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasUpdateReplicationPermission" [disabled]="!selectedRow" (click)="editRule(selectedRow)"><clr-icon shape="pencil" size="16"></clr-icon>&nbsp;{{'REPLICATION.EDIT_POLICY' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasDeleteReplicationPermission" [disabled]="!selectedRow" (click)="deleteRule(selectedRow)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPLICATION.DELETE_POLICY' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasExecuteReplicationPermission" [disabled]="!selectedRow" (click)="replicateRule(selectedRow)"><clr-icon shape="export" size="16"></clr-icon>&nbsp;{{'REPLICATION.REPLICATE' | translate}}</button>
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'name'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'status'">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>

View File

@ -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<boolean>();
@Output() selectOne = new EventEmitter<ReplicationRule>();
@Output() editOne = new EventEmitter<ReplicationRule>();
@ -92,10 +95,10 @@ export class ListReplicationRuleComponent implements OnInit, OnChanges {
enabledComparator: Comparator<ReplicationRule> = new CustomComparator<ReplicationRule>("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);

View File

@ -5,7 +5,7 @@
<div class="form-content" id="clr-wrapper-public">
<clr-checkbox-wrapper>
<input type="checkbox" id="clr-checkbox-wrapper-public" clrCheckbox [(ngModel)]="projectPolicy.Public" name="public"
[disabled]="!hasProjectAdminRole" />
[disabled]="!hasChangeConfigRole" />
<label for="clr-checkbox-wrapper-public">{{ 'PROJECT_CONFIG.PUBLIC_TOGGLE' | translate }}</label>
</clr-checkbox-wrapper>
@ -19,7 +19,7 @@
<div class="form-content">
<div *ngIf="withNotary">
<clr-checkbox-wrapper>
<input type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.ContentTrust" name="content-trust" [disabled]="!hasProjectAdminRole" />
<input type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.ContentTrust" name="content-trust" [disabled]="!hasChangeConfigRole" />
<label>{{ 'PROJECT_CONFIG.CONTENT_TRUST_TOGGLE' | translate }}</label>
</clr-checkbox-wrapper>
@ -28,7 +28,7 @@
<div *ngIf="withClair" id="prevent-vulenrability-image">
<clr-checkbox-wrapper>
<input type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.PreventVulImg" name="prevent-vulenrability-image-input"
[disabled]="!hasProjectAdminRole" />
[disabled]="!hasChangeConfigRole" />
<label>{{ 'PROJECT_CONFIG.PREVENT_VULNERABLE_TOGGLE' | translate }}</label>
</clr-checkbox-wrapper>
@ -52,16 +52,16 @@
<label for="projectPolicyForm">{{ 'PROJECT_CONFIG.SCAN' | translate }}</label>
<div class="form-content">
<clr-checkbox-wrapper id="scan-image-on-push-wrapper">
<input type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.ScanImgOnPush" [disabled]="!hasProjectAdminRole"
<input type="checkbox" clrCheckbox [(ngModel)]="projectPolicy.ScanImgOnPush" [disabled]="!hasChangeConfigRole"
name="scan-image-on-push" />
<label>{{ 'PROJECT_CONFIG.AUTOSCAN_TOGGLE' | translate }}</label>
</clr-checkbox-wrapper>
<div class="chk-explain"><label> {{ 'PROJECT_CONFIG.AUTOSCAN_POLICY' | translate }}</label></div>
</div>
</div>
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="!isValid() || !hasChanges() || !hasProjectAdminRole">{{'BUTTON.SAVE'
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="!isValid() || !hasChanges() || !hasChangeConfigRole">{{'BUTTON.SAVE'
| translate}}</button>
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="!isValid() || !hasChanges() || !hasProjectAdminRole">{{'BUTTON.CANCEL'
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="!isValid() || !hasChanges() || !hasChangeConfigRole">{{'BUTTON.CANCEL'
| translate}}</button>
<confirmation-dialog #cfgConfirmationDialog (confirmAction)="confirmCancel($event)"></confirmation-dialog>
</section>

View File

@ -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();

View File

@ -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;
}

View File

@ -11,9 +11,14 @@
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<hbr-list-replication-rule #listReplicationRule [projectId]="projectId" [isSystemAdmin]="isSystemAdmin" (replicateManual)=replicateManualRule($event)
<hbr-list-replication-rule #listReplicationRule [projectId]="projectId" (replicateManual)=replicateManualRule($event)
(selectOne)="selectOneRule($event)" (hideJobs)="hideJobs()" (openNewRule)="openModal()" (editOne)="openEditRule($event)"
(reload)="reloadRules($event)" [loading]="loading" [withReplicationJob]="withReplicationJob" (redirect)="customRedirect($event)"></hbr-list-replication-rule>
(reload)="reloadRules($event)" [loading]="loading" [withReplicationJob]="withReplicationJob" (redirect)="customRedirect($event)"
[hasCreateReplicationPermission]="hasCreateReplicationPermission"
[hasUpdateReplicationPermission]="hasUpdateReplicationPermission"
[hasDeleteReplicationPermission]="hasDeleteReplicationPermission"
[hasExecuteReplicationPermission]="hasExecuteReplicationPermission"
></hbr-list-replication-rule>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 jobList" [hidden]='hiddenJobList'>
<div *ngIf="withReplicationJob" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">

View File

@ -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<ReplicationRule>();
@Output() openCreateRule = new EventEmitter<any>();

View File

@ -7,7 +7,7 @@
<clr-icon shape="download"></clr-icon>
{{'CONFIG.REGISTRY_CERTIFICATE' | translate | uppercase}}
</a>
<hbr-push-image-button class="push-image-button" [registryUrl]="registryUrl" [projectName]="projectName"></hbr-push-image-button>
<hbr-push-image-button class="push-image-button" *ngIf="hasCreateRepositoryPermission" [registryUrl]="registryUrl" [projectName]="projectName"></hbr-push-image-button>
<hbr-filter [withDivider]="true" filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" (filterEvt)="doSearchRepoNames($event)" [currentValue]="lastFilteredRepoName"></hbr-filter>
<span class="card-btn" (click)="showCard(true)" (mouseenter) ="mouseEnter('card') " (mouseleave) ="mouseLeave('card')">
<clr-icon [ngClass]="{'is-highlight': isCardView || isHovering('card') }" shape="view-cards"></clr-icon>
@ -27,7 +27,7 @@
<clr-dg-action-bar>
<button *ngIf="withAdmiral" type="button" class="btn btn-sm btn-secondary" (click)="provisionItemEvent($event, selectedRow[0])" [disabled]="!(selectedRow.length===1 && hasProjectAdminRole)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.DEPLOY' | translate}}</button>
<button *ngIf="withAdmiral" type="button" class="btn btn-sm btn-secondary" (click)="itemAddInfoEvent($event, selectedRow[0])" [disabled]="!(selectedRow.length===1 && hasProjectAdminRole)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.ADDITIONAL_INFO' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" (click)="deleteRepos(selectedRow)" [disabled]="!(selectedRow.length && hasProjectAdminRole)"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.DELETE' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" (click)="deleteRepos(selectedRow)" [disabled]="!(selectedRow.length)|| !hasDeleteRepositoryPermission"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.DELETE' | translate}}</button>
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'name'">{{'REPOSITORY.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="tagsCountComparator">{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>

View File

@ -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<RepositoryGridviewComponent>;
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();
});

View File

@ -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<number>(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<Tag[]>(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));
}
}

View File

@ -55,7 +55,7 @@
<div id=images-container>
<hbr-tag ngProjectAs="clr-dg-row-detail" (tagClickEvent)="watchTagClickEvt($event)" (signatureOutput)="saveSignatures($event)"
class="sub-grid-custom" [repoName]="repoName" [registryUrl]="registryUrl" [withNotary]="withNotary"
[withClair]="withClair" [withAdmiral]="withAdmiral" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole"
[withClair]="withClair" [withAdmiral]="withAdmiral" [hasSignedIn]="hasSignedIn"
[isGuest]="isGuest" [projectId]="projectId" [memberRoleID]="memberRoleID"></hbr-tag>
</div>
</section>

View File

@ -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 }
]

View File

@ -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";

View File

@ -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<T> {
}
/** @deprecated since 0.11 */
export interface Comparator<T> extends ClrDatagridComparatorInterface<T> {}
export interface Comparator<T> extends ClrDatagridComparatorInterface<T> { }
/** @deprecated since 0.11 */
export interface ClrFilter<T> extends ClrDatagridFilterInterface<T> {}
export interface ClrFilter<T> extends ClrDatagridFilterInterface<T> { }
/** @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;
}

View File

@ -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",
}
},
};

View File

@ -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<object>;
private getPermissionFromBackend(projectId): Observable<object> {
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<boolean> {
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;
}
}

View File

@ -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,

View File

@ -91,15 +91,15 @@
</div>
</section>
<clr-tabs>
<clr-tab *ngIf="withClair">
<clr-tab *ngIf="hasVulnerabilitiesListPermission">
<button clrTabLink class="btn btn-link nav-link" id="tag-vulnerability" [class.active]='isCurrentTabLink("tag-vulnerability")'
type="button" (click)='tabLinkClick("tag-vulnerability")'>{{'REPOSITORY.VULNERABILITY' | translate}}</button>
<clr-tab-content id="content1" *clrIfActive="true">
<hbr-vulnerabilities-grid [repositoryId]="repositoryId" [tagId]="tagId" [withAdminRole]="withAdminRole"></hbr-vulnerabilities-grid>
<hbr-vulnerabilities-grid [repositoryId]="repositoryId" [tagId]="tagId"></hbr-vulnerabilities-grid>
</clr-tab-content>
</clr-tab>
<clr-tab>
<button id="tag-history" clrTabLink class="btn btn-link nav-link" [class.active]='isCurrentTabLink("tag-history")'
<button *ngIf="hasBuildHistoryPermission" id="tag-history" clrTabLink class="btn btn-link nav-link" [class.active]='isCurrentTabLink("tag-history")'
type="button" (click)='tabLinkClick("tag-history")'>{{ 'REPOSITORY.BUILD_HISTORY' | translate }}</button>
<clr-tab-content *clrIfActive>
<hbr-tag-history [repositoryId]="repositoryId" [tagId]="tagId">{{ 'REPOSITORY.BUILD_HISTORY' |

View File

@ -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<TagDetailComponent>;
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();
});

View File

@ -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<any> = new EventEmitter<any>();
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));
}
}

View File

@ -60,7 +60,7 @@
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(canScanNow(selectedRow) && selectedRow.length==1)" (click)="scanNow(selectedRow)"><clr-icon shape="shield-check" size="16"></clr-icon>&nbsp;{{'VULNERABILITY.SCAN_NOW' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!(selectedRow.length==1)" (click)="showDigestId(selectedRow)" ><clr-icon shape="copy" size="16"></clr-icon>&nbsp;{{'REPOSITORY.COPY_DIGEST_ID' | translate}}</button>
<clr-dropdown *ngIf="!withAdmiral">
<button type="button" class="btn btn-sm btn-secondary" clrDropdownTrigger [disabled]="!(selectedRow.length==1 && developerRoleOrAbove)" (click)="addLabels(selectedRow)" ><clr-icon shape="plus" size="16"></clr-icon>{{'REPOSITORY.ADD_LABELS' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" clrDropdownTrigger [disabled]="!(selectedRow.length==1)||!hasAddLabelImagePermission" (click)="addLabels(selectedRow)" ><clr-icon shape="plus" size="16"></clr-icon>{{'REPOSITORY.ADD_LABELS' | translate}}</button>
<clr-dropdown-menu clrPosition="bottom-left" *clrIfOpen>
<div class="filter-grid">
<label class="dropdown-header">{{'REPOSITORY.ADD_LABEL_TO_IMAGE' | translate}}</label>
@ -75,8 +75,8 @@
</div>
</clr-dropdown-menu>
</clr-dropdown>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="!withAdmiral" [disabled]="!(selectedRow.length===1 && guestRoleOrAbove)" (click)="retag(selectedRow)"><clr-icon shape="copy" size="16"></clr-icon>&nbsp;{{'REPOSITORY.RETAG' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasProjectAdminRole" (click)="deleteTags(selectedRow)" [disabled]="!selectedRow.length"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.DELETE' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="!withAdmiral" [disabled]="!(selectedRow.length===1)|| !hasRetagImagePermission" (click)="retag(selectedRow)"><clr-icon shape="copy" size="16"></clr-icon>&nbsp;{{'REPOSITORY.RETAG' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" *ngIf="hasDeleteImagePermission" (click)="deleteTags(selectedRow)" [disabled]="!hasDeleteImagePermission||!selectedRow.length"><clr-icon shape="times" size="16"></clr-icon>&nbsp;{{'REPOSITORY.DELETE' | translate}}</button>
</clr-dg-action-bar>
<clr-dg-column class="flex-max-width" [clrDgField]="'name'">{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'size'">{{'REPOSITORY.SIZE' | translate}}</clr-dg-column>

View File

@ -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<TagComponent>;
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)", () => {
}));
});

View File

@ -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<string> = new Subject<string> ();
stickLabelNameFilter: Subject<string> = new Subject<string> ();
labelNameFilter: Subject<string> = new Subject<string>();
stickLabelNameFilter: Subject<string> = new Subject<string>();
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<Label[]>(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<Label[]>(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<any>(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<any>(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<Tag[]>(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<number>(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();
}
}

View File

@ -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({

View File

@ -10,7 +10,7 @@
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-datagrid>
<clr-dg-action-bar>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!withAdminRole" (click)="scanNow()"><clr-icon shape="shield-check" size="16"></clr-icon>&nbsp;{{'VULNERABILITY.SCAN_NOW' | translate}}</button>
<button type="button" class="btn btn-sm btn-secondary" [disabled]="!hasScanImagePermission" (click)="scanNow()"><clr-icon shape="shield-check" size="16"></clr-icon>&nbsp;{{'VULNERABILITY.SCAN_NOW' | translate}}</button>
</clr-dg-action-bar>
<clr-dg-column [clrDgField]="'id'">{{'VULNERABILITY.GRID.COLUMN_ID' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'severity'">{{'VULNERABILITY.GRID.COLUMN_SEVERITY' | translate}}</clr-dg-column>

View File

@ -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 }
]
});

View File

@ -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);
});
}
}

View File

@ -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 }]
});
}));

View File

@ -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
},

View File

@ -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",

View File

@ -25,7 +25,10 @@
<button id="config-label" clrTabLink>{{'CONFIG.LABEL' | translate }}</button>
<ng-template [(clrIfActive)]="labelActive">
<clr-tab-content id="system_label" *ngIf="!withAdmiral">
<hbr-label [scope]="'g'"></hbr-label>
<hbr-label [scope]="'g'"
[hasCreateLabelPermission]="true"
[hasUpdateLabelPermission]="true"
[hasDeleteLabelPermission]="true"></hbr-label>
</clr-tab-content>
</ng-template>
</clr-tab>

View File

@ -10,8 +10,6 @@
[chartName]='chartName'
[roleName]='roleName'
[hasSignedIn]='hasSignedIn'
[projectRoleID]='project_member_role_id'
[hasProjectAdminRole]='hasProjectAdminRole'
(versionClickEvt)='onVersionClick($event)'
(backEvt)='gotoChartList()'>
</hbr-helm-chart-version>

View File

@ -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 = <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;
}
}

View File

@ -4,6 +4,5 @@
[urlPrefix]='urlPrefix'
[hasSignedIn]='hasSignedIn'
[projectRoleID]='project_member_role_id'
[hasProjectAdminRole]='hasProjectAdminRole'
(chartClickEvt)='onChartClick($event)'>
</hbr-helm-chart>

View File

@ -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 = <Project>(resolverData["projectResolver"]);
this.projectName = project.name;
this.hasProjectAdminRole = project.has_project_admin_role;
this.project_member_role_id = project.current_user_role_id;
}
}

View File

@ -32,6 +32,10 @@
<input type="radio" name="member_role" id="checkrads_project_admin" [value]=1 [(ngModel)]="member.role_id">
<label for="checkrads_project_admin">{{'MEMBER.PROJECT_ADMIN' | translate}}</label>
</div>
<div class="radio">
<input type="radio" name="member_role" id="checkrads_project_master" [value]=4 [(ngModel)]="member.role_id">
<label for="checkrads_project_master">{{'MEMBER.PROJECT_MASTER' | translate}}</label>
</div>
<div class="radio">
<input type="radio" name="member_role" id="checkrads_developer" [value]=2 [(ngModel)]="member.role_id">
<label for="checkrads_developer">{{'MEMBER.DEVELOPER' | translate}}</label>

View File

@ -13,21 +13,22 @@
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-dg-action-bar>
<button class="btn btn-sm btn-secondary" (click)="openAddMemberModal()" [disabled]="!hasProjectAdminRole">
<button class="btn btn-sm btn-secondary" (click)="openAddMemberModal()" [disabled]="!hasCreateMemberPermission">
<span><clr-icon shape="plus" size="16"></clr-icon>&nbsp;{{'MEMBER.USER' | translate }}</span>
</button>
<button class="btn btn-sm btn-secondary" (click)="openAddGroupModal()" [disabled]="!hasProjectAdminRole || !isLdapMode">
<button class="btn btn-sm btn-secondary" (click)="openAddGroupModal()" [disabled]="!hasCreateMemberPermission || !isLdapMode">
<span><clr-icon shape="plus" size="16"></clr-icon>&nbsp;{{'MEMBER.LDAP_GROUP' | translate}}</span>
</button>
<clr-dropdown id='member-action' [clrCloseMenuOnItemClick]="false" class="btn btn-sm btn-link" clrDropdownTrigger>
<span>{{'MEMBER.ACTION' | translate}}<clr-icon shape="caret down"></clr-icon></span>
<clr-dropdown-menu *clrIfOpen>
<label class="dropdown-header">{{'MEMBER.SET_ROLE' | translate}}</label>
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 1)" [disabled]="!(selectedRow.length && hasProjectAdminRole) || onlySelf">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 2)" [disabled]="!(selectedRow.length && hasProjectAdminRole) || onlySelf">{{'MEMBER.DEVELOPER' | translate}}</button>
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 3)" [disabled]="!(selectedRow.length && hasProjectAdminRole) || onlySelf">{{'MEMBER.GUEST' | translate}}</button>
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 1)" [disabled]="!(selectedRow.length && hasUpdateMemberPermission) || onlySelf">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 4)" [disabled]="!(selectedRow.length && hasUpdateMemberPermission) || onlySelf">{{'MEMBER.PROJECT_MASTER' | translate}}</button>
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 2)" [disabled]="!(selectedRow.length && hasUpdateMemberPermission) || onlySelf">{{'MEMBER.DEVELOPER' | translate}}</button>
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 3)" [disabled]="!(selectedRow.length && hasUpdateMemberPermission) || onlySelf">{{'MEMBER.GUEST' | translate}}</button>
<div class="dropdown-divider"></div>
<button clrDropdownItem (click)="openDeleteMembersDialog(selectedRow)" [disabled]="!(selectedRow.length && hasProjectAdminRole) || onlySelf">{{'MEMBER.REMOVE' | translate}}</button>
<button clrDropdownItem (click)="openDeleteMembersDialog(selectedRow)" [disabled]="!(selectedRow.length && hasDeleteMemberPermission) || onlySelf">{{'MEMBER.REMOVE' | translate}}</button>
</clr-dropdown-menu>
</clr-dropdown>
</clr-dg-action-bar>

View File

@ -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 = (<Project>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));
}
}

View File

@ -1,5 +1,5 @@
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 config-top ">
<hbr-project-policy-config [projectId]="projectId" [projectName]="projectName" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole"></hbr-project-policy-config>
<hbr-project-policy-config [projectId]="projectId" [projectName]="projectName" [hasSignedIn]="hasSignedIn"></hbr-project-policy-config>
</div>
</div>

View File

@ -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 = <Project>resolverData['projectResolver'];
this.hasProjectAdminRole = pro.has_project_admin_role;
this.projectName = pro.name;
}
}

View File

@ -4,28 +4,28 @@
<h1 class="custom-h2" sub-header-title>{{currentProject.name}} <span class="role-label" *ngIf="isMember">{{roleName | translate}}</span></h1>
<nav class="subnav sub-nav-bg-color">
<ul class="nav">
<li class="nav-item">
<li class="nav-item" *ngIf="hasRepositoryListPermission">
<a class="nav-link" routerLink="repositories" routerLinkActive="active">{{'PROJECT_DETAIL.REPOSITORIES' | translate}}</a>
</li>
<li *ngIf="withHelmChart" class="nav-item">
<li *ngIf="withHelmChart && hasHelmChartsListPermission" class="nav-item">
<a class="nav-link" routerLink="helm-charts" routerLinkActive="active">{{'PROJECT_DETAIL.HELMCHART' | translate}}</a>
</li>
<li class="nav-item" *ngIf="isSystemAdmin || isMember">
<li class="nav-item" *ngIf="hasMemberListPermission">
<a class="nav-link" routerLink="members" routerLinkActive="active">{{'PROJECT_DETAIL.USERS' | translate}}</a>
</li>
<li class="nav-item" *ngIf="isSProjectAdmin || isSystemAdmin">
<li class="nav-item" *ngIf="hasReplicationListPermission">
<a class="nav-link" routerLink="replications" routerLinkActive="active">{{'PROJECT_DETAIL.REPLICATION' | translate}}</a>
</li>
<li class="nav-item" *ngIf="(isSProjectAdmin || isSystemAdmin) && !withAdmiral">
<li class="nav-item" *ngIf="(hasLabelListPermission && hasLabelCreatePermission) && !withAdmiral">
<a class="nav-link" routerLink="labels" routerLinkActive="active">{{'PROJECT_DETAIL.LABELS' | translate}}</a>
</li>
<li class="nav-item" *ngIf="isSystemAdmin || isMember">
<li class="nav-item" *ngIf="hasLogListPermission">
<a class="nav-link" routerLink="logs" routerLinkActive="active">{{'PROJECT_DETAIL.LOGS' | translate}}</a>
</li>
<li class="nav-item" *ngIf="isSProjectAdmin || isSystemAdmin">
<li class="nav-item" *ngIf="hasRobotListPermission">
<a class="nav-link" routerLink="robot-account" routerLinkActive="active">{{'PROJECT_DETAIL.ROBOT_ACCOUNTS' | translate}}</a>
</li>
<li class="nav-item" *ngIf="isSessionValid && (isSystemAdmin || isMember)">
<li class="nav-item" *ngIf="isSessionValid && (hasConfigurationListPermission)">
<a class="nav-link" routerLink="configs" routerLinkActive="active">{{'PROJECT_DETAIL.CONFIG' | translate}}</a>
</li>
</ul>

View File

@ -11,7 +11,7 @@
// 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 { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Project } from '../project';
@ -19,26 +19,38 @@ import { Project } from '../project';
import { SessionService } from '../../shared/session.service';
import { ProjectService } from '../../project/project.service';
import {AppConfigService} from "../../app-config.service";
import { AppConfigService } from "../../app-config.service";
import { UserPermissionService, USERSTATICPERMISSION, ErrorHandler } from "@harbor/ui";
import { forkJoin } from "rxjs";
@Component({
selector: 'project-detail',
templateUrl: 'project-detail.component.html',
styleUrls: [ 'project-detail.component.scss' ]
selector: 'project-detail',
templateUrl: 'project-detail.component.html',
styleUrls: ['project-detail.component.scss']
})
export class ProjectDetailComponent {
export class ProjectDetailComponent implements OnInit {
hasSignedIn: boolean;
currentProject: Project;
isMember: boolean;
roleName: string;
projectId: number;
hasHelmChartsListPermission: boolean;
hasRepositoryListPermission: boolean;
hasMemberListPermission: boolean;
hasReplicationListPermission: boolean;
hasLabelListPermission: boolean;
hasLabelCreatePermission: boolean;
hasLogListPermission: boolean;
hasConfigurationListPermission: boolean;
hasRobotListPermission: boolean;
constructor(
private route: ActivatedRoute,
private router: Router,
private sessionService: SessionService,
private appConfigService: AppConfigService,
private userPermissionService: UserPermissionService,
private errorHandler: ErrorHandler,
private projectService: ProjectService) {
this.hasSignedIn = this.sessionService.getCurrentUser() !== null;
@ -48,14 +60,42 @@ export class ProjectDetailComponent {
this.roleName = this.currentProject.role_name;
});
}
public get isSystemAdmin(): boolean {
let account = this.sessionService.getCurrentUser();
return account && account.has_admin_role;
ngOnInit() {
this.projectId = this.route.snapshot.params['id'];
this.getPermissionsList(this.projectId);
}
getPermissionsList(projectId: number): void {
let permissionsList = [];
permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.LOG.KEY, USERSTATICPERMISSION.LOG.VALUE.LIST));
permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.CONFIGURATION.KEY, USERSTATICPERMISSION.CONFIGURATION.VALUE.READ));
permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.MEMBER.KEY, USERSTATICPERMISSION.MEMBER.VALUE.LIST));
permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.REPLICATION.KEY, USERSTATICPERMISSION.REPLICATION.VALUE.LIST));
permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.LABEL.KEY, USERSTATICPERMISSION.LABEL.VALUE.LIST));
permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.LIST));
permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.HELM_CHART.KEY, USERSTATICPERMISSION.HELM_CHART.VALUE.LIST));
permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.ROBOT.KEY, USERSTATICPERMISSION.ROBOT.VALUE.LIST));
permissionsList.push(this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.LABEL.KEY, USERSTATICPERMISSION.LABEL.VALUE.CREATE));
forkJoin(...permissionsList).subscribe(Rules => {
this.hasLogListPermission = Rules[0] as boolean;
this.hasConfigurationListPermission = Rules[1] as boolean;
this.hasMemberListPermission = Rules[2] as boolean;
this.hasReplicationListPermission = Rules[3] as boolean;
this.hasLabelListPermission = Rules[4] as boolean;
this.hasRepositoryListPermission = Rules[5] as boolean;
this.hasHelmChartsListPermission = Rules[6] as boolean;
this.hasRobotListPermission = Rules[7] as boolean;
this.hasLabelCreatePermission = Rules[8] as boolean;
public get isSProjectAdmin(): boolean {
return this.currentProject.has_project_admin_role;
}, error => this.errorHandler.error(error));
}
public get isSessionValid(): boolean {

View File

@ -1,5 +1,10 @@
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 label-top">
<hbr-label [projectId]="projectId" [scope]="'p'" [hasProjectAdminRole]="hasProjectAdminRole"></hbr-label>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 label-top">
<hbr-label
[projectId]="projectId"
[scope]="'p'"
[hasCreateLabelPermission]="hasCreateLabelPermission"
[hasUpdateLabelPermission]="hasUpdateLabelPermission"
[hasDeleteLabelPermission]="hasDeleteLabelPermission"></hbr-label>
</div>
</div>

View File

@ -16,6 +16,8 @@ import { ActivatedRoute, Router } from '@angular/router';
import { SessionService } from '../../shared/session.service';
import { SessionUser } from '../../shared/session-user';
import { Project } from '../project';
import { forkJoin } from 'rxjs';
import { UserPermissionService, USERSTATICPERMISSION, ErrorHandler } from "@harbor/ui";
@Component({
selector: 'app-project-config',
@ -29,21 +31,34 @@ export class ProjectLabelComponent implements OnInit {
currentUser: SessionUser;
hasSignedIn: boolean;
hasProjectAdminRole: boolean;
hasCreateLabelPermission: boolean;
hasUpdateLabelPermission: boolean;
hasDeleteLabelPermission: boolean;
constructor(
private route: ActivatedRoute,
private router: Router,
private userPermissionService: UserPermissionService,
private errorHandler: ErrorHandler,
private session: SessionService) {}
ngOnInit() {
this.projectId = +this.route.snapshot.parent.params['id'];
this.currentUser = this.session.getCurrentUser();
this.hasSignedIn = this.session.getCurrentUser() !== null;
let resolverData = this.route.snapshot.parent.data;
if (resolverData) {
let pro: Project = <Project>resolverData['projectResolver'];
this.hasProjectAdminRole = pro.has_project_admin_role;
this.projectName = pro.name;
}
this.getLabelPermissionRule(this.projectId);
}
getLabelPermissionRule(projectId: number): void {
const hasCreateLabelPermission = this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.LABEL.KEY, USERSTATICPERMISSION.LABEL.VALUE.CREATE);
const hasUpdateLabelPermission = this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.LABEL.KEY, USERSTATICPERMISSION.LABEL.VALUE.UPDATE);
const hasDeleteLabelPermission = this.userPermissionService.getPermission(projectId,
USERSTATICPERMISSION.LABEL.KEY, USERSTATICPERMISSION.LABEL.VALUE.DELETE);
forkJoin(hasCreateLabelPermission, hasUpdateLabelPermission, hasDeleteLabelPermission).subscribe(permissions => {
this.hasCreateLabelPermission = permissions[0] as boolean;
this.hasUpdateLabelPermission = permissions[1] as boolean;
this.hasDeleteLabelPermission = permissions[2] as boolean;
}, error => this.errorHandler.error(error));
}
}

View File

@ -14,18 +14,18 @@
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-dg-action-bar>
<button class="btn btn-sm btn-secondary"
(click)="openAddRobotModal()">
<span><clr-icon shape="plus" size="16"></clr-icon>&nbsp;{{'ROBOT_ACCOUNT.NEW_ROBOT_ACCOUNT'
| translate }}</span>
<button class="btn btn-sm btn-secondary" [disabled]="!hasRobotCreatePermission" (click)="openAddRobotModal()">
<span>
<clr-icon shape="plus" size="16"></clr-icon>&nbsp;{{'ROBOT_ACCOUNT.NEW_ROBOT_ACCOUNT'
| translate }}
</span>
</button>
<clr-dropdown [clrCloseMenuOnItemClick]="false" class="btn btn-sm
btn-link"
clrDropdownTrigger>
btn-link" clrDropdownTrigger>
<span>{{'MEMBER.ACTION' | translate}}<clr-icon shape="caret
down"></clr-icon></span>
<clr-dropdown-menu *clrIfOpen>
<button clrDropdownItem [disabled]="!(selectedRow.length ==
<button clrDropdownItem [disabled]="!hasRobotUpdatePermission||!(selectedRow.length ==
1)" (click)="changeAccountStatus(selectedRow)">
<span *ngIf="selectedRow[0] && !selectedRow[0].disabled
|| selectedRow.length!==1">{{'ROBOT_ACCOUNT.DISABLE_ACCOUNT'
@ -35,9 +35,7 @@
| translate}}</span>
</button>
<div class="dropdown-divider"></div>
<button clrDropdownItem
(click)="openDeleteRobotsDialog(selectedRow)"
[disabled]="!selectedRow.length">{{'ROBOT_ACCOUNT.DELETE'
<button clrDropdownItem (click)="openDeleteRobotsDialog(selectedRow)" [disabled]="!hasRobotDeletePermission || !selectedRow.length">{{'ROBOT_ACCOUNT.DELETE'
| translate}}</button>
</clr-dropdown-menu>
</clr-dropdown>
@ -49,12 +47,8 @@
<clr-dg-row *clrDgItems="let r of robots" [clrDgItem]="r">
<clr-dg-cell>{{r.name}}</clr-dg-cell>
<clr-dg-cell [ngSwitch]="r.disabled">
<clr-icon shape="check-circle" *ngSwitchCase="false"
size="20"
class="color-green"></clr-icon>
<clr-icon shape="times-circle" *ngSwitchCase="true"
size="16"
class="color-red red-position"></clr-icon>
<clr-icon shape="check-circle" *ngSwitchCase="false" size="20" class="color-green"></clr-icon>
<clr-icon shape="times-circle" *ngSwitchCase="true" size="16" class="color-red red-position"></clr-icon>
</clr-dg-cell>
<clr-dg-cell>{{r.description}}</clr-dg-cell>
</clr-dg-row>
@ -68,6 +62,5 @@
</clr-dg-footer>
</clr-datagrid>
</div>
<add-robot [projectId]="projectId" [projectName]="projectName"
(create)="createAccount($event)"></add-robot>
</div>
<add-robot [projectId]="projectId" [projectName]="projectName" (create)="createAccount($event)"></add-robot>
</div>

View File

@ -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) {

View File

@ -1,3 +1,12 @@
<div>
<hbr-replication #replicationView [projectId]="projectIdentify" [projectName]="projectName" [isSystemAdmin]="isSystemAdmin" [withReplicationJob]='true' (goToRegistry)="goRegistry()"></hbr-replication>
<hbr-replication #replicationView
[projectId]="projectIdentify"
[projectName]="projectName"
[isSystemAdmin]="isSystemAdmin"
[withReplicationJob]='true'
(goToRegistry)="goRegistry()"
[hasCreateReplicationPermission]="hasCreateReplicationPermission"
[hasUpdateReplicationPermission]="hasUpdateReplicationPermission"
[hasDeleteReplicationPermission]="hasDeleteReplicationPermission"
[hasExecuteReplicationPermission]="hasExecuteReplicationPermission"></hbr-replication>
</div>

View File

@ -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);
});
}
}

View File

@ -1,4 +1,13 @@
<h2 class="custom-h2">{{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}}</h2>
<div class="content-top">
<hbr-replication [withReplicationJob]='true' [isSystemAdmin]="isSystemAdmin" [withAdmiral]="withAdmiral" (goToRegistry)="goRegistry()" (redirect)="customRedirect($event)"></hbr-replication>
<hbr-replication
[withReplicationJob]='true'
[isSystemAdmin]="isSystemAdmin"
[withAdmiral]="withAdmiral"
(goToRegistry)="goRegistry()"
(redirect)="customRedirect($event)"
[hasCreateReplicationPermission]="true"
[hasUpdateReplicationPermission]="true"
[hasDeleteReplicationPermission]="true"
[hasExecuteReplicationPermission]="true"></hbr-replication>
</div>

View File

@ -5,9 +5,9 @@
<a (click)="goBack(repositoryId)">< {{repositoryId}}</a>
</div>
<hbr-tag-detail (backEvt)="goBack($event)"
[tagId]="tagId"
[withClair]="withClair"
[withAdmiral]="withAdmiral"
[repositoryId]="repositoryId"
[withAdminRole]="withAdminRole"></hbr-tag-detail>
[tagId]="tagId"
[withClair]="withClair"
[withAdmiral]="withAdmiral"
[projectId]="projectId"
[repositoryId]="repositoryId"></hbr-tag-detail>
</div>

View File

@ -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]);
}

View File

@ -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);
}

View File

@ -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]);

View File

@ -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> | 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 => {

View File

@ -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<any> {
// 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));
}
/**

View File

@ -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,

View File

@ -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);

View File

@ -219,6 +219,7 @@
"ROLE": "Role",
"SYS_ADMIN": "System Admin",
"PROJECT_ADMIN": "Project Admin",
"PROJECT_MASTER": "Master",
"DEVELOPER": "Developer",
"GUEST": "Guest",
"DELETE": "Delete",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -219,6 +219,7 @@
"ROLE": "角色",
"SYS_ADMIN": "系统管理员",
"PROJECT_ADMIN": "项目管理员",
"PROJECT_MASTER": "维护人员",
"DEVELOPER": "开发人员",
"GUEST": "访客",
"DELETE": "删除",

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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}