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')]