diff --git a/src/portal/src/app/harbor-routing.module.ts b/src/portal/src/app/harbor-routing.module.ts index e8225f02e..bb525c4c6 100644 --- a/src/portal/src/app/harbor-routing.module.ts +++ b/src/portal/src/app/harbor-routing.module.ts @@ -60,8 +60,10 @@ import { HelmChartDetailComponent } from './project/helm-chart/helm-chart-detail import { OidcOnboardComponent } from './oidc-onboard/oidc-onboard.component'; import { LicenseComponent } from './license/license.component'; import { SummaryComponent } from './project/summary/summary.component'; -import { TagRetentionComponent } from './project/tag-retention/tag-retention.component'; -import { ImmutableTagComponent } from './project/immutable-tag/immutable-tag.component'; + +import { TagFeatureIntegrationComponent } from './project/tag-feature-integration/tag-feature-integration.component'; +import { TagRetentionComponent } from './project/tag-feature-integration/tag-retention/tag-retention.component'; +import { ImmutableTagComponent } from './project/tag-feature-integration/immutable-tag/immutable-tag.component'; import { ScannerComponent } from "./project/scanner/scanner.component"; import { InterrogationServicesComponent } from "./interrogation-services/interrogation-services.component"; import { ConfigurationScannerComponent } from "./config/scanner/config-scanner.component"; @@ -210,13 +212,13 @@ const harborRoutes: Routes = [ path: 'projects/:id', component: ProjectDetailComponent, canActivate: [MemberGuard], - canActivateChild: [MemberPermissionGuard], resolve: { projectResolver: ProjectRoutingResolver }, children: [ { path: 'summary', + canActivate: [MemberPermissionGuard], data: { permissionParam: { resource: USERSTATICPERMISSION.PROJECT.KEY, @@ -227,6 +229,7 @@ const harborRoutes: Routes = [ }, { path: 'repositories', + canActivate: [MemberPermissionGuard], data: { permissionParam: { resource: USERSTATICPERMISSION.REPOSITORY.KEY, @@ -237,6 +240,7 @@ const harborRoutes: Routes = [ }, { path: 'helm-charts', + canActivate: [MemberPermissionGuard], data: { permissionParam: { resource: USERSTATICPERMISSION.HELM_CHART.KEY, @@ -247,6 +251,7 @@ const harborRoutes: Routes = [ }, { path: 'repositories/:repo/tags', + canActivate: [MemberPermissionGuard], data: { permissionParam: { resource: USERSTATICPERMISSION.REPOSITORY.KEY, @@ -257,6 +262,7 @@ const harborRoutes: Routes = [ }, { path: 'members', + canActivate: [MemberPermissionGuard], data: { permissionParam: { resource: USERSTATICPERMISSION.MEMBER.KEY, @@ -267,6 +273,7 @@ const harborRoutes: Routes = [ }, { path: 'logs', + canActivate: [MemberPermissionGuard], data: { permissionParam: { resource: USERSTATICPERMISSION.LOG.KEY, @@ -277,6 +284,7 @@ const harborRoutes: Routes = [ }, { path: 'labels', + canActivate: [MemberPermissionGuard], data: { permissionParam: { resource: USERSTATICPERMISSION.LABEL.KEY, @@ -287,6 +295,7 @@ const harborRoutes: Routes = [ }, { path: 'configs', + canActivate: [MemberPermissionGuard], data: { permissionParam: { resource: USERSTATICPERMISSION.CONFIGURATION.KEY, @@ -297,6 +306,7 @@ const harborRoutes: Routes = [ }, { path: 'robot-account', + canActivate: [MemberPermissionGuard], data: { permissionParam: { resource: USERSTATICPERMISSION.ROBOT.KEY, @@ -306,27 +316,31 @@ const harborRoutes: Routes = [ component: RobotAccountComponent }, { - path: 'tag-retention', + path: 'tag-strategy', + canActivate: [MemberPermissionGuard], data: { permissionParam: { resource: USERSTATICPERMISSION.TAG_RETENTION.KEY, action: USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ } }, - component: TagRetentionComponent - }, - { - path: 'immutable-tag', - data: { - permissionParam: { - resource: USERSTATICPERMISSION.TAG_RETENTION.KEY, - action: USERSTATICPERMISSION.TAG_RETENTION.VALUE.READ - } - }, - component: ImmutableTagComponent + component: TagFeatureIntegrationComponent, + children: [ + { + path: 'tag-retention', + component: TagRetentionComponent + }, + { + path: 'immutable-tag', + component: ImmutableTagComponent + }, + { path: '', redirectTo: 'tag-retention', pathMatch: 'full' }, + + ] }, { path: 'webhook', + canActivate: [MemberPermissionGuard], data: { permissionParam: { resource: USERSTATICPERMISSION.WEBHOOK.KEY, @@ -337,6 +351,7 @@ const harborRoutes: Routes = [ }, { path: 'scanner', + canActivate: [MemberPermissionGuard], data: { permissionParam: { resource: USERSTATICPERMISSION.SCANNER.KEY, diff --git a/src/portal/src/app/project/project-detail/project-detail.component.html b/src/portal/src/app/project/project-detail/project-detail.component.html index b58659667..05374f69f 100644 --- a/src/portal/src/app/project/project-detail/project-detail.component.html +++ b/src/portal/src/app/project/project-detail/project-detail.component.html @@ -1,45 +1,18 @@ < {{'PROJECT_DETAIL.PROJECTS' | translate}} < {{'SEARCH.BACK' | translate}} -

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

- - +

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

+ + + + + + + + + + + \ No newline at end of file diff --git a/src/portal/src/app/project/project-detail/project-detail.component.scss b/src/portal/src/app/project/project-detail/project-detail.component.scss index a7177fe39..a1d16063e 100644 --- a/src/portal/src/app/project/project-detail/project-detail.component.scss +++ b/src/portal/src/app/project/project-detail/project-detail.component.scss @@ -12,12 +12,44 @@ } .role-label { - color: #CCCCCC; + color: #cccccc; font-size: 14px; font-style: italic; letter-spacing: 0.01em; } -.backStyle{ +.backStyle { color: #007cbb; font-size: 12px; - cursor: pointer;} \ No newline at end of file + cursor: pointer; +} + +button { + outline: none; +} +#project-tabs { + .clear-default-active { + box-shadow: none; + &:hover { + box-shadow: 0 -3px 0 #0077b8 inset; + } + } +} + +.tabs { + .nav-link { + padding: 0; + } +} + +.in-overflow { + ::ng-deep { + .tabs-overflow { + > .nav-item { + > button { + box-shadow: 0 -3px 0 #0077b8 inset; + color: 0077b8; + } + } + } + } +} diff --git a/src/portal/src/app/project/project-detail/project-detail.component.spec.ts b/src/portal/src/app/project/project-detail/project-detail.component.spec.ts index 2ab0573cd..1ce8f4dc5 100644 --- a/src/portal/src/app/project/project-detail/project-detail.component.spec.ts +++ b/src/portal/src/app/project/project-detail/project-detail.component.spec.ts @@ -35,18 +35,24 @@ describe('ProjectDetailComponent', () => { }; const mockUserPermissionService = { getPermission() { - return of(true); + return of(true); } - }; + }; const mockProjectService = null; const mockErrorHandler = { error() { } - }; + }; const mockActivatedRoute = { RouterparamMap: of({ get: (key) => 'value' }), snapshot: { - params: { id: 1 }, - data: 1 + params: { id: 1 }, + data: 1, + children: [ + { + routeConfig: + { path: "" } + } + ] }, data: of({ projectResolver: { diff --git a/src/portal/src/app/project/project-detail/project-detail.component.ts b/src/portal/src/app/project/project-detail/project-detail.component.ts index 80bc7fbfc..2422f8041 100644 --- a/src/portal/src/app/project/project-detail/project-detail.component.ts +++ b/src/portal/src/app/project/project-detail/project-detail.component.ts @@ -44,6 +44,74 @@ export class ProjectDetailComponent implements OnInit { hasTagRetentionPermission: boolean; hasWebhookListPermission: boolean; hasScannerReadPermission: boolean; + tabLinkNavList = [ + { + linkName: "summary", + tabLinkInOverflow: false, + showTabName: "PROJECT_DETAIL.SUMMARY", + permissions: () => this.hasProjectReadPermission + }, + { + linkName: "repositories", + tabLinkInOverflow: false, + showTabName: "PROJECT_DETAIL.REPOSITORIES", + permissions: () => this.hasRepositoryListPermission + }, + { + linkName: "helm-charts", + tabLinkInOverflow: false, + showTabName: "PROJECT_DETAIL.HELMCHART", + permissions: () => this.withHelmChart && this.hasHelmChartsListPermission + }, + { + linkName: "members", + tabLinkInOverflow: false, + showTabName: "PROJECT_DETAIL.USERS", + permissions: () => this.hasMemberListPermission + }, + { + linkName: "labels", + tabLinkInOverflow: false, + showTabName: "PROJECT_DETAIL.LABELS", + permissions: () => (this.hasLabelListPermission && this.hasLabelCreatePermission) && !this.withAdmiral + }, + { + linkName: "scanner", + tabLinkInOverflow: false, + showTabName: "SCANNER.SCANNER", + permissions: () => this.hasScannerReadPermission + }, + { + linkName: "configs", + tabLinkInOverflow: false, + showTabName: "PROJECT_DETAIL.CONFIG", + permissions: () => this.isSessionValid && this.hasConfigurationListPermission + }, + { + linkName: "tag-strategy", + tabLinkInOverflow: true, + showTabName: "PROJECT_DETAIL.TAG_STRATEGY", + permissions: () => this.hasTagRetentionPermission + }, + { + linkName: "robot-account", + tabLinkInOverflow: true, + showTabName: "PROJECT_DETAIL.ROBOT_ACCOUNTS", + permissions: () => this.hasRobotListPermission + }, + { + linkName: "webhook", + tabLinkInOverflow: true, + showTabName: "PROJECT_DETAIL.WEBHOOKS", + permissions: () => this.hasWebhookListPermission + }, + { + linkName: "logs", + tabLinkInOverflow: true, + showTabName: "PROJECT_DETAIL.LOGS", + permissions: () => this.hasLogListPermission + } + ]; constructor( private route: ActivatedRoute, private router: Router, @@ -117,5 +185,13 @@ export class ProjectDetailComponent implements OnInit { } this.router.navigate(['/harbor', 'projects']); } + isDefaultTab(tab, index) { + return this.route.snapshot.children[0].routeConfig.path !== tab.linkName && index === 0; + } + isTabLinkInOverFlow() { + return this.tabLinkNavList.some(tab => { + return tab.tabLinkInOverflow && this.route.snapshot.children[0].routeConfig.path === tab.linkName; + }); + } } diff --git a/src/portal/src/app/project/project.module.ts b/src/portal/src/app/project/project.module.ts index b20a8ceec..e1034f86f 100644 --- a/src/portal/src/app/project/project.module.ts +++ b/src/portal/src/app/project/project.module.ts @@ -18,7 +18,7 @@ import { SharedModule } from '../shared/shared.module'; import { RepositoryModule } from '../repository/repository.module'; import { ReplicationModule } from '../replication/replication.module'; import { SummaryModule } from './summary/summary.module'; -import { ImmutableTagModule } from './immutable-tag/immutable-tag.module'; +import { TagFeatureIntegrationModule } from './tag-feature-integration/tag-feature-integration.module'; import { LogModule } from '../log/log.module'; import { ProjectComponent } from './project.component'; @@ -41,9 +41,6 @@ import { HelmChartModule } from './helm-chart/helm-chart.module'; import { RobotAccountComponent } from './robot-account/robot-account.component'; import { AddRobotComponent } from './robot-account/add-robot/add-robot.component'; import { AddHttpAuthGroupComponent } from './member/add-http-auth-group/add-http-auth-group.component'; -import { TagRetentionComponent } from "./tag-retention/tag-retention.component"; -import { AddRuleComponent } from "./tag-retention/add-rule/add-rule.component"; -import { TagRetentionService } from "./tag-retention/tag-retention.service"; import { WebhookService } from './webhook/webhook.service'; import { WebhookComponent } from './webhook/webhook.component'; import { AddWebhookComponent } from './webhook/add-webhook/add-webhook.component'; @@ -60,7 +57,7 @@ import { ConfigScannerService } from "../config/scanner/config-scanner.service"; RouterModule, HelmChartModule, SummaryModule, - ImmutableTagModule + TagFeatureIntegrationModule, ], declarations: [ ProjectComponent, @@ -75,15 +72,13 @@ import { ConfigScannerService } from "../config/scanner/config-scanner.service"; RobotAccountComponent, AddRobotComponent, AddHttpAuthGroupComponent, - TagRetentionComponent, - AddRuleComponent, WebhookComponent, AddWebhookComponent, AddWebhookFormComponent, ScannerComponent, ], exports: [ProjectComponent, ListProjectComponent], - providers: [ProjectRoutingResolver, MemberService, RobotService, TagRetentionService, WebhookService, ConfigScannerService] + providers: [ProjectRoutingResolver, MemberService, RobotService, WebhookService, ConfigScannerService] }) export class ProjectModule { diff --git a/src/portal/src/app/project/immutable-tag/add-rule/add-rule.component.html b/src/portal/src/app/project/tag-feature-integration/immutable-tag/add-rule/add-rule.component.html similarity index 100% rename from src/portal/src/app/project/immutable-tag/add-rule/add-rule.component.html rename to src/portal/src/app/project/tag-feature-integration/immutable-tag/add-rule/add-rule.component.html diff --git a/src/portal/src/app/project/immutable-tag/add-rule/add-rule.component.scss b/src/portal/src/app/project/tag-feature-integration/immutable-tag/add-rule/add-rule.component.scss similarity index 100% rename from src/portal/src/app/project/immutable-tag/add-rule/add-rule.component.scss rename to src/portal/src/app/project/tag-feature-integration/immutable-tag/add-rule/add-rule.component.scss diff --git a/src/portal/src/app/project/immutable-tag/add-rule/add-rule.component.spec.ts b/src/portal/src/app/project/tag-feature-integration/immutable-tag/add-rule/add-rule.component.spec.ts similarity index 97% rename from src/portal/src/app/project/immutable-tag/add-rule/add-rule.component.spec.ts rename to src/portal/src/app/project/tag-feature-integration/immutable-tag/add-rule/add-rule.component.spec.ts index c33e06f95..1a6061580 100644 --- a/src/portal/src/app/project/immutable-tag/add-rule/add-rule.component.spec.ts +++ b/src/portal/src/app/project/tag-feature-integration/immutable-tag/add-rule/add-rule.component.spec.ts @@ -7,7 +7,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { ImmutableTagService } from '../immutable-tag.service'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; -import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component"; +import { InlineAlertComponent } from "../../../../shared/inline-alert/inline-alert.component"; import { ImmutableRetentionRule } from "../../tag-retention/retention"; describe('AddRuleComponent', () => { let component: AddRuleComponent; diff --git a/src/portal/src/app/project/immutable-tag/add-rule/add-rule.component.ts b/src/portal/src/app/project/tag-feature-integration/immutable-tag/add-rule/add-rule.component.ts similarity index 97% rename from src/portal/src/app/project/immutable-tag/add-rule/add-rule.component.ts rename to src/portal/src/app/project/tag-feature-integration/immutable-tag/add-rule/add-rule.component.ts index dbc07b1e5..b9597cf65 100644 --- a/src/portal/src/app/project/immutable-tag/add-rule/add-rule.component.ts +++ b/src/portal/src/app/project/tag-feature-integration/immutable-tag/add-rule/add-rule.component.ts @@ -7,8 +7,8 @@ import { } from "@angular/core"; import { ImmutableRetentionRule, RuleMetadate } from "../../tag-retention/retention"; import { ImmutableTagService } from "../immutable-tag.service"; -import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component"; -import { compareValue } from "../../../../lib/utils/utils"; +import { compareValue } from "../../../../../lib/utils/utils"; +import { InlineAlertComponent } from "../../../../shared/inline-alert/inline-alert.component"; const EXISTING_RULE = "TAG_RETENTION.EXISTING_RULE"; const INVALID_RULE = "TAG_RETENTION.INVALID_RULE"; diff --git a/src/portal/src/app/project/immutable-tag/immutable-tag.component.html b/src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.component.html similarity index 100% rename from src/portal/src/app/project/immutable-tag/immutable-tag.component.html rename to src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.component.html diff --git a/src/portal/src/app/project/immutable-tag/immutable-tag.component.scss b/src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.component.scss similarity index 100% rename from src/portal/src/app/project/immutable-tag/immutable-tag.component.scss rename to src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.component.scss diff --git a/src/portal/src/app/project/immutable-tag/immutable-tag.component.spec.ts b/src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.component.spec.ts similarity index 97% rename from src/portal/src/app/project/immutable-tag/immutable-tag.component.spec.ts rename to src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.component.spec.ts index d3fe19acd..0d2a97840 100644 --- a/src/portal/src/app/project/immutable-tag/immutable-tag.component.spec.ts +++ b/src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.component.spec.ts @@ -1,5 +1,5 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { InlineAlertComponent } from "../../shared/inline-alert/inline-alert.component"; +import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component"; import { ImmutableTagComponent } from './immutable-tag.component'; import { ClarityModule } from '@clr/angular'; @@ -12,8 +12,8 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { ActivatedRoute } from '@angular/router'; import { of, throwError } from 'rxjs'; -import { DefaultErrorHandler, ErrorHandler } from "../../../lib/utils/error-handler"; -import { clone } from "../../../lib/utils/utils"; +import { DefaultErrorHandler, ErrorHandler } from "../../../../lib/utils/error-handler"; +import { clone } from "../../../../lib/utils/utils"; describe('ImmutableTagComponent', () => { let component: ImmutableTagComponent; @@ -224,7 +224,9 @@ describe('ImmutableTagComponent', () => { paramMap: of({ get: (key) => 'value' }), snapshot: { parent: { - params: { id: 1 } + parent: { + params: { id: 1 } + } }, data: 1 } diff --git a/src/portal/src/app/project/immutable-tag/immutable-tag.component.ts b/src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.component.ts similarity index 96% rename from src/portal/src/app/project/immutable-tag/immutable-tag.component.ts rename to src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.component.ts index 1cbcb2a47..1b8d035b2 100644 --- a/src/portal/src/app/project/immutable-tag/immutable-tag.component.ts +++ b/src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.component.ts @@ -4,8 +4,8 @@ import { AddRuleComponent } from "./add-rule/add-rule.component"; import { ImmutableTagService } from "./immutable-tag.service"; import { ImmutableRetentionRule } from "../tag-retention/retention"; import { finalize } from "rxjs/operators"; -import { ErrorHandler } from "../../../lib/utils/error-handler"; -import { clone } from "../../../lib/utils/utils"; +import { ErrorHandler } from "../../../../lib/utils/error-handler"; +import { clone } from "../../../../lib/utils/utils"; @Component({ selector: 'app-immutable-tag', @@ -30,7 +30,7 @@ export class ImmutableTagComponent implements OnInit { } ngOnInit() { - this.projectId = +this.route.snapshot.parent.params['id']; + this.projectId = +this.route.snapshot.parent.parent.params['id']; this.getRules(); this.getMetadata(); } diff --git a/src/portal/src/app/project/immutable-tag/immutable-tag.module.ts b/src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.module.ts similarity index 90% rename from src/portal/src/app/project/immutable-tag/immutable-tag.module.ts rename to src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.module.ts index 995f71cbf..b19d93696 100644 --- a/src/portal/src/app/project/immutable-tag/immutable-tag.module.ts +++ b/src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.module.ts @@ -1,6 +1,6 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { SharedModule } from '../../shared/shared.module'; +import { SharedModule } from '../../../shared/shared.module'; import { ImmutableTagComponent } from './immutable-tag.component'; import { ImmutableTagService } from './immutable-tag.service'; diff --git a/src/portal/src/app/project/immutable-tag/immutable-tag.service.spec.ts b/src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.service.spec.ts similarity index 100% rename from src/portal/src/app/project/immutable-tag/immutable-tag.service.spec.ts rename to src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.service.spec.ts diff --git a/src/portal/src/app/project/immutable-tag/immutable-tag.service.ts b/src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.service.ts similarity index 95% rename from src/portal/src/app/project/immutable-tag/immutable-tag.service.ts rename to src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.service.ts index 77f9e3037..f3e10e764 100644 --- a/src/portal/src/app/project/immutable-tag/immutable-tag.service.ts +++ b/src/portal/src/app/project/tag-feature-integration/immutable-tag/immutable-tag.service.ts @@ -3,8 +3,9 @@ import { HttpClient } from "@angular/common/http"; import { ImmutableRetentionRule, RuleMetadate } from "../tag-retention/retention"; import { Observable, throwError as observableThrowError } from "rxjs"; import { map, catchError } from "rxjs/operators"; -import { Project } from "../project"; -import { HTTP_JSON_OPTIONS } from "../../../lib/utils/utils"; +import { Project } from "../../project"; +import { HTTP_JSON_OPTIONS } from "../../../../lib/utils/utils"; + @Injectable() export class ImmutableTagService { diff --git a/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.component.html b/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.component.html new file mode 100644 index 000000000..c086b88b3 --- /dev/null +++ b/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.component.html @@ -0,0 +1,11 @@ +
+
+ + +
+
+ + +
+
+ \ No newline at end of file diff --git a/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.component.scss b/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.component.scss new file mode 100644 index 000000000..bccf1a691 --- /dev/null +++ b/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.component.scss @@ -0,0 +1,17 @@ +.btn-tag-integration { + margin-top: 1.5rem; + margin-bottom: 1rem; + .nav-link { + height: 100%; + width: 100%; + display: inline-block; + padding-left: 0.5rem; + padding-right: 0.5rem; + text-decoration: none; + color: #0077b8; + } + .active { + background: #0077b8; + color: #fff; + } +} diff --git a/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.component.spec.ts b/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.component.spec.ts new file mode 100644 index 000000000..c2e1bc582 --- /dev/null +++ b/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.component.spec.ts @@ -0,0 +1,29 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TagFeatureIntegrationComponent } from './tag-feature-integration.component'; +import { RouterModule } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterTestingModule } from '@angular/router/testing'; + +describe('TagFeatureIntegrationComponent', () => { + let component: TagFeatureIntegrationComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TagFeatureIntegrationComponent ], + imports: [ RouterModule, TranslateModule.forRoot(), RouterTestingModule ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TagFeatureIntegrationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.component.ts b/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.component.ts new file mode 100644 index 000000000..1df8a54f6 --- /dev/null +++ b/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-tag-feature-integration', + templateUrl: './tag-feature-integration.component.html', + styleUrls: ['./tag-feature-integration.component.scss'] +}) +export class TagFeatureIntegrationComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.module.ts b/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.module.ts new file mode 100644 index 000000000..93dc97b32 --- /dev/null +++ b/src/portal/src/app/project/tag-feature-integration/tag-feature-integration.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TagFeatureIntegrationComponent } from './tag-feature-integration.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { TagRetentionComponent } from "./tag-retention/tag-retention.component"; +import { ImmutableTagModule } from "./immutable-tag/immutable-tag.module"; +import { ClarityModule } from '@clr/angular'; +import { SharedModule } from '../../shared/shared.module'; +import { AddRuleComponent } from "./tag-retention/add-rule/add-rule.component"; +import { RouterModule } from '@angular/router'; +import { TagRetentionService } from './tag-retention/tag-retention.service'; + + + +@NgModule({ + declarations: [TagFeatureIntegrationComponent, TagRetentionComponent, AddRuleComponent], + imports: [ + CommonModule, + TranslateModule, + ImmutableTagModule, + ClarityModule, + SharedModule, + RouterModule + ], + providers: [ + TagRetentionService + ] +}) +export class TagFeatureIntegrationModule { } diff --git a/src/portal/src/app/project/tag-retention/add-rule/add-rule.component.html b/src/portal/src/app/project/tag-feature-integration/tag-retention/add-rule/add-rule.component.html similarity index 100% rename from src/portal/src/app/project/tag-retention/add-rule/add-rule.component.html rename to src/portal/src/app/project/tag-feature-integration/tag-retention/add-rule/add-rule.component.html diff --git a/src/portal/src/app/project/tag-retention/add-rule/add-rule.component.scss b/src/portal/src/app/project/tag-feature-integration/tag-retention/add-rule/add-rule.component.scss similarity index 100% rename from src/portal/src/app/project/tag-retention/add-rule/add-rule.component.scss rename to src/portal/src/app/project/tag-feature-integration/tag-retention/add-rule/add-rule.component.scss diff --git a/src/portal/src/app/project/tag-retention/add-rule/add-rule.component.spec.ts b/src/portal/src/app/project/tag-feature-integration/tag-retention/add-rule/add-rule.component.spec.ts similarity index 95% rename from src/portal/src/app/project/tag-retention/add-rule/add-rule.component.spec.ts rename to src/portal/src/app/project/tag-feature-integration/tag-retention/add-rule/add-rule.component.spec.ts index 6005d8354..b6886ebb7 100644 --- a/src/portal/src/app/project/tag-retention/add-rule/add-rule.component.spec.ts +++ b/src/portal/src/app/project/tag-feature-integration/tag-retention/add-rule/add-rule.component.spec.ts @@ -9,7 +9,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TagRetentionService } from "../tag-retention.service"; -import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component"; +import { InlineAlertComponent } from "../../../../shared/inline-alert/inline-alert.component"; import { delay } from 'rxjs/operators'; describe('AddRuleComponent', () => { let component: AddRuleComponent; diff --git a/src/portal/src/app/project/tag-retention/add-rule/add-rule.component.ts b/src/portal/src/app/project/tag-feature-integration/tag-retention/add-rule/add-rule.component.ts similarity index 98% rename from src/portal/src/app/project/tag-retention/add-rule/add-rule.component.ts rename to src/portal/src/app/project/tag-feature-integration/tag-retention/add-rule/add-rule.component.ts index 9cc4ebf3c..769cb55cc 100644 --- a/src/portal/src/app/project/tag-retention/add-rule/add-rule.component.ts +++ b/src/portal/src/app/project/tag-feature-integration/tag-retention/add-rule/add-rule.component.ts @@ -20,8 +20,8 @@ import { } from "@angular/core"; import { Retention, Rule, RuleMetadate } from "../retention"; import { TagRetentionService } from "../tag-retention.service"; -import { InlineAlertComponent } from "../../../shared/inline-alert/inline-alert.component"; -import { compareValue } from "../../../../lib/utils/utils"; +import { InlineAlertComponent } from "../../../../shared/inline-alert/inline-alert.component"; +import { compareValue } from "../../../../../lib/utils/utils"; const EXISTING_RULE = "TAG_RETENTION.EXISTING_RULE"; const ILLEGAL_RULE = "TAG_RETENTION.ILLEGAL_RULE"; diff --git a/src/portal/src/app/project/tag-retention/retention.ts b/src/portal/src/app/project/tag-feature-integration/tag-retention/retention.ts similarity index 100% rename from src/portal/src/app/project/tag-retention/retention.ts rename to src/portal/src/app/project/tag-feature-integration/tag-retention/retention.ts diff --git a/src/portal/src/app/project/tag-retention/tag-retention.component.html b/src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.component.html similarity index 100% rename from src/portal/src/app/project/tag-retention/tag-retention.component.html rename to src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.component.html diff --git a/src/portal/src/app/project/tag-retention/tag-retention.component.scss b/src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.component.scss similarity index 100% rename from src/portal/src/app/project/tag-retention/tag-retention.component.scss rename to src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.component.scss diff --git a/src/portal/src/app/project/tag-retention/tag-retention.component.spec.ts b/src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.component.spec.ts similarity index 90% rename from src/portal/src/app/project/tag-retention/tag-retention.component.spec.ts rename to src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.component.spec.ts index 37d141bd9..517d01c51 100644 --- a/src/portal/src/app/project/tag-retention/tag-retention.component.spec.ts +++ b/src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.component.spec.ts @@ -13,7 +13,7 @@ import { AddRuleComponent } from "./add-rule/add-rule.component"; import { TagRetentionService } from "./tag-retention.service"; import { RuleMetadate, Retention } from './retention'; import { delay } from 'rxjs/operators'; -import { ErrorHandler } from "../../../lib/utils/error-handler"; +import { ErrorHandler } from "../../../../lib/utils/error-handler"; describe('TagRetentionComponent', () => { let component: TagRetentionComponent; @@ -42,11 +42,13 @@ describe('TagRetentionComponent', () => { const mockActivatedRoute = { snapshot: { parent: { - params: { id: 1 }, - data: { - projectResolver: { - metadata: { - retention_id: 1 + parent: { + params: { id: 1 }, + data: { + projectResolver: { + metadata: { + retention_id: 1 + } } } } @@ -54,7 +56,7 @@ describe('TagRetentionComponent', () => { } }; const mockErrorHandler = { - error: () => {} + error: () => { } }; beforeEach(async(() => { TestBed.configureTestingModule({ diff --git a/src/portal/src/app/project/tag-retention/tag-retention.component.ts b/src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.component.ts similarity index 97% rename from src/portal/src/app/project/tag-retention/tag-retention.component.ts rename to src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.component.ts index bc7c8fd9f..74ce80b65 100644 --- a/src/portal/src/app/project/tag-retention/tag-retention.component.ts +++ b/src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.component.ts @@ -17,12 +17,14 @@ import { AddRuleComponent } from "./add-rule/add-rule.component"; import { ClrDatagridStringFilterInterface } from "@clr/angular"; import { TagRetentionService } from "./tag-retention.service"; import { Retention, Rule } from "./retention"; -import { Project } from "../project"; + +import { Project } from "../../project"; + import { finalize } from "rxjs/operators"; -import { CronScheduleComponent } from "../../../lib/components/cron-schedule"; -import { ErrorHandler } from "../../../lib/utils/error-handler"; -import { OriginCron } from "../../../lib/services"; -import { clone } from "../../../lib/utils/utils"; +import { CronScheduleComponent } from "../../../../lib/components/cron-schedule"; +import { ErrorHandler } from "../../../../lib/utils/error-handler"; +import { OriginCron } from "../../../../lib/services"; +import { clone } from "../../../../lib/utils/utils"; const MIN = 60000; const SEC = 1000; @@ -112,7 +114,7 @@ export class TagRetentionComponent implements OnInit { } ngOnInit() { - this.projectId = +this.route.snapshot.parent.params['id']; + this.projectId = +this.route.snapshot.parent.parent.params['id']; this.retention.scope = { level: "project", ref: this.projectId diff --git a/src/portal/src/app/project/tag-retention/tag-retention.service.spec.ts b/src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.service.spec.ts similarity index 100% rename from src/portal/src/app/project/tag-retention/tag-retention.service.spec.ts rename to src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.service.spec.ts diff --git a/src/portal/src/app/project/tag-retention/tag-retention.service.ts b/src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.service.ts similarity index 98% rename from src/portal/src/app/project/tag-retention/tag-retention.service.ts rename to src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.service.ts index ffc516e1a..71ba707da 100644 --- a/src/portal/src/app/project/tag-retention/tag-retention.service.ts +++ b/src/portal/src/app/project/tag-feature-integration/tag-retention/tag-retention.service.ts @@ -16,8 +16,8 @@ import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http"; import { Retention, RuleMetadate } from "./retention"; import { Observable, throwError as observableThrowError } from "rxjs"; import { map, catchError } from "rxjs/operators"; -import { Project } from "../project"; -import { buildHttpRequestOptionsWithObserveResponse } from "../../../lib/utils/utils"; +import { Project } from "../../project"; +import { buildHttpRequestOptionsWithObserveResponse } from "../../../../lib/utils/utils"; @Injectable() export class TagRetentionService { diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index 184a879ab..d9258cdb5 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -248,7 +248,8 @@ "HELMCHART": "Helm Charts", "ROBOT_ACCOUNTS": "Robot Accounts", "WEBHOOKS": "Webhooks", - "IMMUTABLE_TAG": "Tag Immutability" + "IMMUTABLE_TAG": "Tag Immutability", + "TAG_STRATEGY": "Tag Strategy" }, "PROJECT_CONFIG": { "REGISTRY": "Project registry", diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index 7cbdc0012..6a9f765c1 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -249,7 +249,8 @@ "HELMCHART": "Helm Charts", "ROBOT_ACCOUNTS": "Robot Accounts", "WEBHOOKS": "Webhooks", - "IMMUTABLE_TAG": "Tag Immutability" + "IMMUTABLE_TAG": "Tag Immutability", + "TAG_STRATEGY": "Tag Strategy" }, "PROJECT_CONFIG": { "REGISTRY": "Registro de proyectos", diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index d9d0690a1..19dd92263 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -242,7 +242,8 @@ "HELMCHART": "Helm Charts", "ROBOT_ACCOUNTS": "Robot Accounts", "WEBHOOKS": "Webhooks", - "IMMUTABLE_TAG": "Tag Immutability" + "IMMUTABLE_TAG": "Tag Immutability", + "TAG_STRATEGY": "Tag Strategy" }, "PROJECT_CONFIG": { "REGISTRY": "Dépôt du Projet", diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index 2c935fae7..edd014fa7 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -246,7 +246,8 @@ "HELMCHART": "Helm Charts", "ROBOT_ACCOUNTS": "Robot Accounts", "WEBHOOKS": "Webhooks", - "IMMUTABLE_TAG": "Tag Immutability" + "IMMUTABLE_TAG": "Tag Immutability", + "TAG_STRATEGY": "Tag Strategy" }, "PROJECT_CONFIG": { "REGISTRY": "Registro do Projeto", diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index d9c0fcf75..6faa8887d 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -248,7 +248,8 @@ "HELMCHART": "Helm Tabloları", "ROBOT_ACCOUNTS": "Robot Hesapları", "WEBHOOKS": "Ağ Kancaları", - "IMMUTABLE_TAG": "Tag Immutability" + "IMMUTABLE_TAG": "Tag Immutability", + "TAG_STRATEGY": "Tag Strategy" }, "PROJECT_CONFIG": { "REGISTRY": "Proje kaydı", diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index a28117e40..04d1f426e 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -247,7 +247,8 @@ "HELMCHART": "Helm Charts", "ROBOT_ACCOUNTS": "机器人账户", "WEBHOOKS": "Webhooks", - "IMMUTABLE_TAG": "不可变的Tag" + "IMMUTABLE_TAG": "不可变的Tag", + "TAG_STRATEGY": "Tag 策略" }, "PROJECT_CONFIG": { "REGISTRY": "项目仓库", diff --git a/tests/resources/Harbor-Pages/Project-Members_Elements.robot b/tests/resources/Harbor-Pages/Project-Members_Elements.robot index c30342c49..e1b73850f 100644 --- a/tests/resources/Harbor-Pages/Project-Members_Elements.robot +++ b/tests/resources/Harbor-Pages/Project-Members_Elements.robot @@ -16,7 +16,7 @@ Documentation This resource provides any keywords related to the Harbor private registry appliance *** Variables *** -${project_member_tag_xpath} //clr-main-container//project-detail/nav/ul//a[contains(.,'Members')] +${project_member_tag_xpath} //clr-main-container//project-detail/clr-tabs//a[contains(.,'Members')] ${project_member_add_button_xpath} //project-detail//button[contains(.,'User')] ${project_member_add_username_xpath} //*[@id='member_name'] ${project_member_add_admin_xpath} /html/body/harbor-app/harbor-shell/clr-main-container/div/div/project-detail/ng-component/div/div[1]/div/div[1]/add-member/clr-modal/div/div[1]/div/div[1]/div/div[2]/form/section/div[2]/div[1]/label diff --git a/tests/resources/Harbor-Pages/Project-Webhooks.robot b/tests/resources/Harbor-Pages/Project-Webhooks.robot index 1d913498e..aad86f15b 100644 --- a/tests/resources/Harbor-Pages/Project-Webhooks.robot +++ b/tests/resources/Harbor-Pages/Project-Webhooks.robot @@ -6,6 +6,7 @@ Resource ../../resources/Util.robot *** Keywords *** Switch To Project Webhooks + Switch To Project Tab Overflow Retry Element Click xpath=//project-detail//a[contains(.,'Webhooks')] Sleep 1 diff --git a/tests/resources/Harbor-Pages/Project.robot b/tests/resources/Harbor-Pages/Project.robot index fa49260bb..35a223f32 100644 --- a/tests/resources/Harbor-Pages/Project.robot +++ b/tests/resources/Harbor-Pages/Project.robot @@ -45,6 +45,7 @@ Create An New Project With New User #It's the log of project. Go To Project Log + Switch To Project Tab Overflow Retry Element Click xpath=${project_log_xpath} Sleep 2 @@ -66,7 +67,11 @@ Switch To Project Configuration Sleep 1 Switch To Tag Retention - Retry Element Click xpath=${project_tag_retention_xpath} + Switch To Project Tab Overflow + Retry Element Click xpath=${project_tag_strategy_xpath} + Sleep 1 +Switch To Project Tab Overflow + Retry Element Click xpath=${project_tab_overflow_btn} Sleep 1 Navigate To Projects diff --git a/tests/resources/Harbor-Pages/Project_Elements.robot b/tests/resources/Harbor-Pages/Project_Elements.robot index 5cb8c9543..96737bd42 100644 --- a/tests/resources/Harbor-Pages/Project_Elements.robot +++ b/tests/resources/Harbor-Pages/Project_Elements.robot @@ -23,10 +23,11 @@ ${project_save_css} html body.no-scrolling harbor-app harbor-shell clr-main-con ${log_xpath} //clr-main-container//clr-vertical-nav//a[contains(.,'Logs')] ${projects_xpath} //clr-main-container//clr-vertical-nav//a[contains(.,'Projects')] ${project_replication_xpath} //project-detail//a[contains(.,'Replication')] -${project_log_xpath} //project-detail//li[contains(.,'Logs')] -${project_member_xpath} //project-detail//li[contains(.,'Members')] +${project_log_xpath} //project-detail//a[contains(.,'Logs')] +${project_member_xpath} //project-detail//a[contains(.,'Members')] ${project_config_tabsheet} xpath=//project-detail//a[contains(.,'Configuration')] -${project_tag_retention_xpath} //nav//li//a[contains(.,'Tag')] +${project_tag_strategy_xpath} //clr-tabs//a[contains(.,'Tag')] +${project_tab_overflow_btn} //clr-tabs//li//button[contains(@class,"dropdown-toggle")] ${create_project_CANCEL_button_xpath} xpath=//button[contains(.,'CANCEL')] ${create_project_OK_button_xpath} xpath=//button[contains(.,'OK')]