mirror of https://github.com/goharbor/harbor.git
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 <sshijun@vmware.com>
This commit is contained in:
parent
efd9632e96
commit
6c1fbde2a8
|
@ -73,6 +73,7 @@ export class ProjectDetailComponent
|
||||||
hasConfigurationListPermission: boolean;
|
hasConfigurationListPermission: boolean;
|
||||||
hasRobotListPermission: boolean;
|
hasRobotListPermission: boolean;
|
||||||
hasTagRetentionPermission: boolean;
|
hasTagRetentionPermission: boolean;
|
||||||
|
hasTagImmutablePermission: boolean;
|
||||||
hasWebhookListPermission: boolean;
|
hasWebhookListPermission: boolean;
|
||||||
hasScannerReadPermission: boolean;
|
hasScannerReadPermission: boolean;
|
||||||
hasP2pProviderReadPermission: boolean;
|
hasP2pProviderReadPermission: boolean;
|
||||||
|
@ -128,7 +129,9 @@ export class ProjectDetailComponent
|
||||||
linkName: 'tag-strategy',
|
linkName: 'tag-strategy',
|
||||||
tabLinkInOverflow: false,
|
tabLinkInOverflow: false,
|
||||||
showTabName: 'PROJECT_DETAIL.POLICY',
|
showTabName: 'PROJECT_DETAIL.POLICY',
|
||||||
permissions: () => this.hasTagRetentionPermission,
|
permissions: () =>
|
||||||
|
this.hasTagRetentionPermission ||
|
||||||
|
this.hasTagImmutablePermission,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
linkName: 'robot-account',
|
linkName: 'robot-account',
|
||||||
|
@ -297,6 +300,13 @@ export class ProjectDetailComponent
|
||||||
USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ
|
USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
permissionsList.push(
|
||||||
|
this.userPermissionService.getPermission(
|
||||||
|
projectId,
|
||||||
|
USERSTATICPERMISSION.IMMUTABLE_TAG.KEY,
|
||||||
|
USERSTATICPERMISSION.IMMUTABLE_TAG.VALUE.LIST
|
||||||
|
)
|
||||||
|
);
|
||||||
permissionsList.push(
|
permissionsList.push(
|
||||||
this.userPermissionService.getPermission(
|
this.userPermissionService.getPermission(
|
||||||
projectId,
|
projectId,
|
||||||
|
@ -339,6 +349,7 @@ export class ProjectDetailComponent
|
||||||
this.hasRobotListPermission,
|
this.hasRobotListPermission,
|
||||||
this.hasLabelCreatePermission,
|
this.hasLabelCreatePermission,
|
||||||
this.hasTagRetentionPermission,
|
this.hasTagRetentionPermission,
|
||||||
|
this.hasTagImmutablePermission,
|
||||||
this.hasWebhookListPermission,
|
this.hasWebhookListPermission,
|
||||||
this.hasScannerReadPermission,
|
this.hasScannerReadPermission,
|
||||||
this.hasP2pProviderReadPermission,
|
this.hasP2pProviderReadPermission,
|
||||||
|
|
|
@ -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> | boolean {
|
||||||
|
const projectId = route.parent.parent.parent.params['id'];
|
||||||
|
const permission = route.data.permissionParam as UserPrivilegeServeItem;
|
||||||
|
return this.memberPermissionGuard.checkPermission(
|
||||||
|
projectId,
|
||||||
|
permission
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,19 @@
|
||||||
<div class="btn-group btn-tag-integration">
|
<div class="btn-group btn-tag-integration">
|
||||||
<div
|
<button
|
||||||
class="radio btn"
|
[disabled]="!hasTagRetentionPermission"
|
||||||
routerLink="tag-retention"
|
routerLink="tag-retention"
|
||||||
routerLinkActive="checked">
|
routerLinkActive="btn-primary"
|
||||||
<input type="radio" name="btn-group-demo-radios" id="btn-retention" />
|
class="btn"
|
||||||
<label class="strategy-nav-link" for="btn-retention">{{
|
id="btn-retention">
|
||||||
'TAG_RETENTION.TAG_RETENTION' | translate
|
{{ 'TAG_RETENTION.TAG_RETENTION' | translate }}
|
||||||
}}</label>
|
</button>
|
||||||
</div>
|
<button
|
||||||
<div
|
[disabled]="!hasTagImmutablePermission"
|
||||||
class="radio btn"
|
|
||||||
routerLink="immutable-tag"
|
routerLink="immutable-tag"
|
||||||
routerLinkActive="checked">
|
routerLinkActive="btn-primary"
|
||||||
<input type="radio" name="btn-group-demo-radios" id="btn-immutable" />
|
class="btn"
|
||||||
<label class="strategy-nav-link" for="btn-immutable">{{
|
id="btn-immutable">
|
||||||
'PROJECT_DETAIL.IMMUTABLE_TAG' | translate
|
{{ 'PROJECT_DETAIL.IMMUTABLE_TAG' | translate }}
|
||||||
}}</label>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|
|
@ -1,17 +1,4 @@
|
||||||
.btn-tag-integration {
|
.btn-tag-integration {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
margin-bottom: 1rem;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,42 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { TagFeatureIntegrationComponent } from './tag-feature-integration.component';
|
import { TagFeatureIntegrationComponent } from './tag-feature-integration.component';
|
||||||
import { SharedTestingModule } from '../../../shared/shared.module';
|
import { SharedTestingModule } from '../../../shared/shared.module';
|
||||||
|
import { UserPermissionService } from '../../../shared/services';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
describe('TagFeatureIntegrationComponent', () => {
|
describe('TagFeatureIntegrationComponent', () => {
|
||||||
let component: TagFeatureIntegrationComponent;
|
let component: TagFeatureIntegrationComponent;
|
||||||
let fixture: ComponentFixture<TagFeatureIntegrationComponent>;
|
let fixture: ComponentFixture<TagFeatureIntegrationComponent>;
|
||||||
|
|
||||||
|
const mockActivatedRoute = {
|
||||||
|
snapshot: {
|
||||||
|
parent: {
|
||||||
|
parent: {
|
||||||
|
params: { id: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mockUserPermissionService = {
|
||||||
|
getPermission() {
|
||||||
|
return of(true);
|
||||||
|
},
|
||||||
|
};
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [TagFeatureIntegrationComponent],
|
declarations: [TagFeatureIntegrationComponent],
|
||||||
imports: [SharedTestingModule],
|
imports: [SharedTestingModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: UserPermissionService,
|
||||||
|
useValue: mockUserPermissionService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: mockActivatedRoute,
|
||||||
|
},
|
||||||
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -22,4 +49,11 @@ describe('TagFeatureIntegrationComponent', () => {
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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({
|
@Component({
|
||||||
selector: 'app-tag-feature-integration',
|
selector: 'app-tag-feature-integration',
|
||||||
templateUrl: './tag-feature-integration.component.html',
|
templateUrl: './tag-feature-integration.component.html',
|
||||||
styleUrls: ['./tag-feature-integration.component.scss'],
|
styleUrls: ['./tag-feature-integration.component.scss'],
|
||||||
})
|
})
|
||||||
export class TagFeatureIntegrationComponent {
|
export class TagFeatureIntegrationComponent implements OnInit {
|
||||||
constructor() {}
|
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<Observable<boolean>> = [];
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { ImmutableTagComponent } from './immutable-tag/immutable-tag.component';
|
||||||
import { ImmutableTagService } from './immutable-tag/immutable-tag.service';
|
import { ImmutableTagService } from './immutable-tag/immutable-tag.service';
|
||||||
import { AddImmutableRuleComponent } from './immutable-tag/add-rule/add-immutable-rule.component';
|
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 { 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 = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
@ -17,10 +19,24 @@ const routes: Routes = [
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'tag-retention',
|
path: 'tag-retention',
|
||||||
|
canActivate: [TagFeatureGuardService],
|
||||||
|
data: {
|
||||||
|
permissionParam: {
|
||||||
|
resource: USERSTATICPERMISSION.TAG_RETENTION.KEY,
|
||||||
|
action: USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ,
|
||||||
|
},
|
||||||
|
},
|
||||||
component: TagRetentionComponent,
|
component: TagRetentionComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'immutable-tag',
|
path: 'immutable-tag',
|
||||||
|
canActivate: [TagFeatureGuardService],
|
||||||
|
data: {
|
||||||
|
permissionParam: {
|
||||||
|
resource: USERSTATICPERMISSION.IMMUTABLE_TAG.KEY,
|
||||||
|
action: USERSTATICPERMISSION.IMMUTABLE_TAG.VALUE.LIST,
|
||||||
|
},
|
||||||
|
},
|
||||||
component: ImmutableTagComponent,
|
component: ImmutableTagComponent,
|
||||||
},
|
},
|
||||||
{ path: '', redirectTo: 'tag-retention', pathMatch: 'full' },
|
{ path: '', redirectTo: 'tag-retention', pathMatch: 'full' },
|
||||||
|
|
|
@ -27,27 +27,7 @@ export class MemberPermissionGuard implements CanActivate, CanActivateChild {
|
||||||
): Observable<boolean> | boolean {
|
): Observable<boolean> | boolean {
|
||||||
const projectId = route.parent.params['id'];
|
const projectId = route.parent.params['id'];
|
||||||
const permission = route.data.permissionParam as UserPrivilegeServeItem;
|
const permission = route.data.permissionParam as UserPrivilegeServeItem;
|
||||||
return new Observable(observer => {
|
return this.checkPermission(projectId, permission);
|
||||||
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);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canActivateChild(
|
canActivateChild(
|
||||||
|
@ -56,4 +36,30 @@ export class MemberPermissionGuard implements CanActivate, CanActivateChild {
|
||||||
): Observable<boolean> | boolean {
|
): Observable<boolean> | boolean {
|
||||||
return this.canActivate(route, state);
|
return this.canActivate(route, state);
|
||||||
}
|
}
|
||||||
|
checkPermission(
|
||||||
|
projectId: number,
|
||||||
|
permission: UserPrivilegeServeItem
|
||||||
|
): Observable<boolean> {
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
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 {
|
hbr-result-tip-histogram {
|
||||||
.inner {
|
.inner {
|
||||||
background-color: $hbr-result-tip-histogram-inner-bg-color;
|
background-color: $hbr-result-tip-histogram-inner-bg-color;
|
||||||
|
|
|
@ -28,8 +28,6 @@ $filter-divider-bg-color: #495865;
|
||||||
/* stylelint-disable */
|
/* stylelint-disable */
|
||||||
$selectBox-option-hover-bg-color-start: #4aaed9;
|
$selectBox-option-hover-bg-color-start: #4aaed9;
|
||||||
$selectBox-option-hover-bg-color-end: #0077b8;
|
$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;
|
$hbr-result-tip-histogram-inner-bg-color: #21333b;
|
||||||
$harbor-icon-translate-x: 0;
|
$harbor-icon-translate-x: 0;
|
||||||
$harbor-icon-drop-shadow-x: 58px;
|
$harbor-icon-drop-shadow-x: 58px;
|
||||||
|
|
|
@ -29,8 +29,6 @@ $filter-divider-bg-color: #ccc;
|
||||||
/* stylelint-disable */
|
/* stylelint-disable */
|
||||||
$selectBox-option-hover-bg-color-start: #f5f5f5;
|
$selectBox-option-hover-bg-color-start: #f5f5f5;
|
||||||
$selectBox-option-hover-bg-color-end: #e8e8e8;
|
$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;
|
$hbr-result-tip-histogram-inner-bg-color: #fff;
|
||||||
$harbor-icon-translate-x: 100%;
|
$harbor-icon-translate-x: 100%;
|
||||||
$harbor-icon-drop-shadow-x: -56px;
|
$harbor-icon-drop-shadow-x: -56px;
|
||||||
|
|
Loading…
Reference in New Issue