From 6c1fbde2a808fd99960b0b6442a28e92cd69f302 Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Fri, 8 Jul 2022 11:38:54 +0800 Subject: [PATCH] Developer role should be able to view tag-retention rules (#17138) Developer role should be able to view tag-rerention rules Signed-off-by: AllForNothing --- .../project-detail.component.ts | 13 ++++- .../tag-feature-guard.service.ts | 28 +++++++++++ .../tag-feature-integration.component.html | 30 ++++++------ .../tag-feature-integration.component.scss | 13 ----- .../tag-feature-integration.component.spec.ts | 34 +++++++++++++ .../tag-feature-integration.component.ts | 40 ++++++++++++++-- .../tag-feature-integration.module.ts | 16 +++++++ ...ember-permission-guard-activate.service.ts | 48 +++++++++++-------- src/portal/src/css/common.scss | 7 --- src/portal/src/css/dark-theme.scss | 2 - src/portal/src/css/light-theme.scss | 2 - 11 files changed, 168 insertions(+), 65 deletions(-) create mode 100644 src/portal/src/app/base/project/tag-feature-integration/tag-feature-guard.service.ts diff --git a/src/portal/src/app/base/project/project-detail/project-detail.component.ts b/src/portal/src/app/base/project/project-detail/project-detail.component.ts index b71c8a15f..073a0abc9 100644 --- a/src/portal/src/app/base/project/project-detail/project-detail.component.ts +++ b/src/portal/src/app/base/project/project-detail/project-detail.component.ts @@ -73,6 +73,7 @@ export class ProjectDetailComponent hasConfigurationListPermission: boolean; hasRobotListPermission: boolean; hasTagRetentionPermission: boolean; + hasTagImmutablePermission: boolean; hasWebhookListPermission: boolean; hasScannerReadPermission: boolean; hasP2pProviderReadPermission: boolean; @@ -128,7 +129,9 @@ export class ProjectDetailComponent linkName: 'tag-strategy', tabLinkInOverflow: false, showTabName: 'PROJECT_DETAIL.POLICY', - permissions: () => this.hasTagRetentionPermission, + permissions: () => + this.hasTagRetentionPermission || + this.hasTagImmutablePermission, }, { linkName: 'robot-account', @@ -297,6 +300,13 @@ export class ProjectDetailComponent USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ ) ); + permissionsList.push( + this.userPermissionService.getPermission( + projectId, + USERSTATICPERMISSION.IMMUTABLE_TAG.KEY, + USERSTATICPERMISSION.IMMUTABLE_TAG.VALUE.LIST + ) + ); permissionsList.push( this.userPermissionService.getPermission( projectId, @@ -339,6 +349,7 @@ export class ProjectDetailComponent this.hasRobotListPermission, this.hasLabelCreatePermission, this.hasTagRetentionPermission, + this.hasTagImmutablePermission, this.hasWebhookListPermission, this.hasScannerReadPermission, this.hasP2pProviderReadPermission, diff --git a/src/portal/src/app/base/project/tag-feature-integration/tag-feature-guard.service.ts b/src/portal/src/app/base/project/tag-feature-integration/tag-feature-guard.service.ts new file mode 100644 index 000000000..15afab12e --- /dev/null +++ b/src/portal/src/app/base/project/tag-feature-integration/tag-feature-guard.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, + ActivatedRouteSnapshot, + RouterStateSnapshot, +} from '@angular/router'; +import { Observable } from 'rxjs'; +import { UserPrivilegeServeItem } from 'src/app/shared/services/interface'; +import { MemberPermissionGuard } from '../../../shared/router-guard/member-permission-guard-activate.service'; + +@Injectable({ + providedIn: 'root', +}) +export class TagFeatureGuardService implements CanActivate { + constructor(private memberPermissionGuard: MemberPermissionGuard) {} + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable | boolean { + const projectId = route.parent.parent.parent.params['id']; + const permission = route.data.permissionParam as UserPrivilegeServeItem; + return this.memberPermissionGuard.checkPermission( + projectId, + permission + ); + } +} diff --git a/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.html b/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.html index 2e259a08e..c42ee6482 100644 --- a/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.html +++ b/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.html @@ -1,21 +1,19 @@
-
- - -
-
+ {{ 'TAG_RETENTION.TAG_RETENTION' | translate }} + +
+ routerLinkActive="btn-primary" + class="btn" + id="btn-immutable"> + {{ 'PROJECT_DETAIL.IMMUTABLE_TAG' | translate }} +
diff --git a/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.scss b/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.scss index 9b6f2b8ab..1db3247e2 100644 --- a/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.scss +++ b/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.scss @@ -1,17 +1,4 @@ .btn-tag-integration { margin-top: 1.5rem; margin-bottom: 1rem; - - .strategy-nav-link { - height: 100%; - width: 100%; - display: inline-block; - padding-left: 0.5rem; - padding-right: 0.5rem; - text-decoration: none; - } -} - -.checked { - color: #fff; } diff --git a/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.spec.ts b/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.spec.ts index 024bfc23c..4907250dd 100644 --- a/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.spec.ts +++ b/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.spec.ts @@ -1,15 +1,42 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TagFeatureIntegrationComponent } from './tag-feature-integration.component'; import { SharedTestingModule } from '../../../shared/shared.module'; +import { UserPermissionService } from '../../../shared/services'; +import { ActivatedRoute } from '@angular/router'; +import { of } from 'rxjs'; describe('TagFeatureIntegrationComponent', () => { let component: TagFeatureIntegrationComponent; let fixture: ComponentFixture; + const mockActivatedRoute = { + snapshot: { + parent: { + parent: { + params: { id: 1 }, + }, + }, + }, + }; + const mockUserPermissionService = { + getPermission() { + return of(true); + }, + }; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [TagFeatureIntegrationComponent], imports: [SharedTestingModule], + providers: [ + { + provide: UserPermissionService, + useValue: mockUserPermissionService, + }, + { + provide: ActivatedRoute, + useValue: mockActivatedRoute, + }, + ], }).compileComponents(); }); @@ -22,4 +49,11 @@ describe('TagFeatureIntegrationComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should get project id and permissions', async () => { + await fixture.whenStable(); + expect(component.projectId).toEqual(1); + expect(component.hasTagImmutablePermission).toBeTruthy(); + expect(component.hasTagRetentionPermission).toBeTruthy(); + }); }); diff --git a/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.ts b/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.ts index ac8418780..6bf991395 100644 --- a/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.ts +++ b/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.component.ts @@ -1,10 +1,44 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { + UserPermissionService, + USERSTATICPERMISSION, +} from '../../../shared/services'; +import { forkJoin, Observable } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-tag-feature-integration', templateUrl: './tag-feature-integration.component.html', styleUrls: ['./tag-feature-integration.component.scss'], }) -export class TagFeatureIntegrationComponent { - constructor() {} +export class TagFeatureIntegrationComponent implements OnInit { + projectId: number; + hasTagRetentionPermission: boolean; + hasTagImmutablePermission: boolean; + constructor( + private userPermissionService: UserPermissionService, + private route: ActivatedRoute + ) {} + ngOnInit() { + this.projectId = this.route.snapshot.parent.parent.params['id']; + const permissionsList: Array> = []; + permissionsList.push( + this.userPermissionService.getPermission( + this.projectId, + USERSTATICPERMISSION.TAG_RETENTION.KEY, + USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ + ) + ); + permissionsList.push( + this.userPermissionService.getPermission( + this.projectId, + USERSTATICPERMISSION.IMMUTABLE_TAG.KEY, + USERSTATICPERMISSION.IMMUTABLE_TAG.VALUE.LIST + ) + ); + forkJoin(permissionsList).subscribe(Rules => { + [this.hasTagRetentionPermission, this.hasTagImmutablePermission] = + Rules; + }); + } } diff --git a/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.module.ts b/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.module.ts index 84359a89d..cff4b33de 100644 --- a/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.module.ts +++ b/src/portal/src/app/base/project/tag-feature-integration/tag-feature-integration.module.ts @@ -9,6 +9,8 @@ import { ImmutableTagComponent } from './immutable-tag/immutable-tag.component'; import { ImmutableTagService } from './immutable-tag/immutable-tag.service'; import { AddImmutableRuleComponent } from './immutable-tag/add-rule/add-immutable-rule.component'; import { TagRetentionTasksComponent } from './tag-retention/tag-retention-tasks/tag-retention-tasks/tag-retention-tasks.component'; +import { USERSTATICPERMISSION } from '../../../shared/services'; +import { TagFeatureGuardService } from './tag-feature-guard.service'; const routes: Routes = [ { @@ -17,10 +19,24 @@ const routes: Routes = [ children: [ { path: 'tag-retention', + canActivate: [TagFeatureGuardService], + data: { + permissionParam: { + resource: USERSTATICPERMISSION.TAG_RETENTION.KEY, + action: USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ, + }, + }, component: TagRetentionComponent, }, { path: 'immutable-tag', + canActivate: [TagFeatureGuardService], + data: { + permissionParam: { + resource: USERSTATICPERMISSION.IMMUTABLE_TAG.KEY, + action: USERSTATICPERMISSION.IMMUTABLE_TAG.VALUE.LIST, + }, + }, component: ImmutableTagComponent, }, { path: '', redirectTo: 'tag-retention', pathMatch: 'full' }, diff --git a/src/portal/src/app/shared/router-guard/member-permission-guard-activate.service.ts b/src/portal/src/app/shared/router-guard/member-permission-guard-activate.service.ts index 568265f45..84c8aa3ad 100644 --- a/src/portal/src/app/shared/router-guard/member-permission-guard-activate.service.ts +++ b/src/portal/src/app/shared/router-guard/member-permission-guard-activate.service.ts @@ -27,27 +27,7 @@ export class MemberPermissionGuard implements CanActivate, CanActivateChild { ): Observable | boolean { const projectId = route.parent.params['id']; const permission = route.data.permissionParam as UserPrivilegeServeItem; - return new Observable(observer => { - this.userPermission - .getPermission( - projectId, - permission.resource, - permission.action - ) - .subscribe( - permissionRouter => { - if (!permissionRouter) { - this.router.navigate([CommonRoutes.HARBOR_DEFAULT]); - } - observer.next(permissionRouter); - }, - error => { - this.router.navigate([CommonRoutes.HARBOR_DEFAULT]); - observer.next(false); - this.errorHandler.error(error); - } - ); - }); + return this.checkPermission(projectId, permission); } canActivateChild( @@ -56,4 +36,30 @@ export class MemberPermissionGuard implements CanActivate, CanActivateChild { ): Observable | boolean { return this.canActivate(route, state); } + checkPermission( + projectId: number, + permission: UserPrivilegeServeItem + ): Observable { + return new Observable(observer => { + this.userPermission + .getPermission( + projectId, + permission.resource, + permission.action + ) + .subscribe({ + next: permissionRouter => { + if (!permissionRouter) { + this.router.navigate([CommonRoutes.HARBOR_DEFAULT]); + } + observer.next(permissionRouter); + }, + error: error => { + this.router.navigate([CommonRoutes.HARBOR_DEFAULT]); + observer.next(false); + this.errorHandler.error(error); + }, + }); + }); + } } diff --git a/src/portal/src/css/common.scss b/src/portal/src/css/common.scss index c1c0152e3..23ac97e09 100644 --- a/src/portal/src/css/common.scss +++ b/src/portal/src/css/common.scss @@ -159,13 +159,6 @@ hbr-tag { background-image: linear-gradient(180deg,$selectBox-option-hover-bg-color-start 0,$selectBox-option-hover-bg-color-end) !important; } -.btn-tag-integration { - .active { - background: $btn-tag-integration-active-bg-color; - color: $btn-tag-integration-active-color; - } -} - hbr-result-tip-histogram { .inner { background-color: $hbr-result-tip-histogram-inner-bg-color; diff --git a/src/portal/src/css/dark-theme.scss b/src/portal/src/css/dark-theme.scss index e68cf23b6..f8aaf81e7 100644 --- a/src/portal/src/css/dark-theme.scss +++ b/src/portal/src/css/dark-theme.scss @@ -28,8 +28,6 @@ $filter-divider-bg-color: #495865; /* stylelint-disable */ $selectBox-option-hover-bg-color-start: #4aaed9; $selectBox-option-hover-bg-color-end: #0077b8; -$btn-tag-integration-active-bg-color: #4aaed9; -$btn-tag-integration-active-color: #000; $hbr-result-tip-histogram-inner-bg-color: #21333b; $harbor-icon-translate-x: 0; $harbor-icon-drop-shadow-x: 58px; diff --git a/src/portal/src/css/light-theme.scss b/src/portal/src/css/light-theme.scss index 29323a86c..205e886ae 100644 --- a/src/portal/src/css/light-theme.scss +++ b/src/portal/src/css/light-theme.scss @@ -29,8 +29,6 @@ $filter-divider-bg-color: #ccc; /* stylelint-disable */ $selectBox-option-hover-bg-color-start: #f5f5f5; $selectBox-option-hover-bg-color-end: #e8e8e8; -$btn-tag-integration-active-bg-color: #0077b8; -$btn-tag-integration-active-color: #fff; $hbr-result-tip-histogram-inner-bg-color: #fff; $harbor-icon-translate-x: 100%; $harbor-icon-drop-shadow-x: -56px;