Add ruote resue strategy (#14524)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Will Sun 2021-04-06 16:58:00 +08:00 committed by GitHub
parent fbf2409c78
commit acba15210b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 424 additions and 84 deletions

View File

@ -16,9 +16,10 @@
<global-message [isAppLevel]="true"></global-message>
<navigator (showAccountSettingsModal)="openModal($event)" (showDialogModalAction)="openModal($event)"></navigator>
<div class="content-container">
<div class="content-area" [class.container-override]="showSearch"
<div #scrollDiv class="content-area" [class.container-override]="showSearch"
[class.content-area-override]="!shouldOverrideContent"
[class.start-content-padding]="shouldOverrideContent">
[class.start-content-padding]="shouldOverrideContent"
(scroll)="publishScrollEvent()">
<global-message [isAppLevel]="false"></global-message>
<!-- Only appear when searching -->
<search-result></search-result>

View File

@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
import { Component, OnInit, ViewChild, OnDestroy, ElementRef, ChangeDetectorRef } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Subscription } from "rxjs";
import { AppConfigService } from '../../services/app-config.service';
@ -28,6 +28,7 @@ import { THEME_ARRAY, ThemeInterface } from "../../services/theme";
import { clone } from "../../shared/units/utils";
import { ThemeService } from "../../services/theme.service";
import { AccountSettingsModalComponent } from "../account-settings/account-settings-modal.component";
import { EventService, HarborEvent } from "../../services/event-service/event.service";
const HAS_SHOWED_SCANNER_INFO: string = 'hasShowScannerInfo';
const YES: string = 'yes';
@ -67,6 +68,8 @@ export class HarborShellComponent implements OnInit, OnDestroy {
themeArray: ThemeInterface[] = clone(THEME_ARRAY);
styleMode = this.themeArray[0].showStyle;
@ViewChild('scrollDiv') scrollDiv: ElementRef;
scrollToPositionSub: Subscription;
constructor(
private route: ActivatedRoute,
private router: Router,
@ -75,9 +78,19 @@ export class HarborShellComponent implements OnInit, OnDestroy {
private appConfigService: AppConfigService,
private scannerService: ConfigScannerService,
public theme: ThemeService,
private event: EventService,
private cd: ChangeDetectorRef
) { }
ngOnInit() {
if (!this.scrollToPositionSub) {
this.scrollToPositionSub = this.event.subscribe( HarborEvent.SCROLL_TO_POSITION, scrollTop => {
if (this.scrollDiv && this.scrollDiv.nativeElement) {
this.cd.detectChanges();
this.scrollDiv.nativeElement.scrollTop = scrollTop;
}
});
}
if (this.appConfigService.isLdapMode()) {
this.isLdapMode = true;
} else if (this.appConfigService.isHttpAuthMode()) {
@ -102,6 +115,14 @@ export class HarborShellComponent implements OnInit, OnDestroy {
this.styleMode = localStorage.getItem(HAS_STYLE_MODE);
}
}
publishScrollEvent() {
if (this.scrollDiv && this.scrollDiv.nativeElement) {
this.event.publish(HarborEvent.SCROLL, {
url: this.router.url,
scrollTop: this.scrollDiv.nativeElement.scrollTop
});
}
}
closeInfo() {
if (localStorage) {
localStorage.setItem(HAS_SHOWED_SCANNER_INFO, YES);
@ -125,6 +146,10 @@ export class HarborShellComponent implements OnInit, OnDestroy {
if (this.searchCloseSub) {
this.searchCloseSub.unsubscribe();
}
if (this.scrollToPositionSub) {
this.scrollToPositionSub.unsubscribe();
this.scrollToPositionSub = null;
}
}
public get shouldOverrideContent(): boolean {

View File

@ -22,10 +22,15 @@ import { ReplicationTasksRoutingResolverService } from "../../../services/routin
import { RouterModule, Routes } from "@angular/router";
import { ListReplicationRuleComponent } from "./replication/list-replication-rule/list-replication-rule.component";
import { CreateEditRuleComponent } from "./replication/create-edit-rule/create-edit-rule.component";
import { RouteConfigId } from "../../../route-reuse-strategy/harbor-route-reuse-strategy";
const routes: Routes = [
{
path: '',
component: TotalReplicationPageComponent
component: TotalReplicationPageComponent,
data : {
reuse: true,
routeConfigId: RouteConfigId.REPLICATION_PAGE
}
},
{
path: ':id/tasks',
@ -33,6 +38,9 @@ const routes: Routes = [
resolve: {
replicationTasksRoutingResolver: ReplicationTasksRoutingResolverService
},
data : {
routeConfigId: RouteConfigId.REPLICATION_TASKS_PAGE
}
}
];
@NgModule({

View File

@ -1,15 +1,11 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { TranslateService } from '@ngx-translate/core';
import { TotalReplicationPageComponent } from './total-replication-page.component';
import {Router, ActivatedRoute} from "@angular/router";
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ClarityModule } from '@clr/angular';
import { FormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import {SessionService} from "../../../shared/services/session.service";
import {AppConfigService} from "../../../services/app-config.service";
import { SharedTestingModule } from "../../../shared/shared.module";
describe('TotalReplicationPageComponent', () => {
let component: TotalReplicationPageComponent;
@ -26,7 +22,10 @@ describe('TotalReplicationPageComponent', () => {
}
};
const mockRouter = {
navigate: () => { }
navigate: () => {},
events: {
subscribe: () => {}
}
};
const mockActivatedRoute = null;
beforeEach(waitForAsync(() => {
@ -35,17 +34,10 @@ describe('TotalReplicationPageComponent', () => {
CUSTOM_ELEMENTS_SCHEMA
],
imports: [
BrowserAnimationsModule,
ClarityModule,
TranslateModule.forRoot(),
FormsModule,
RouterTestingModule,
NoopAnimationsModule,
HttpClientTestingModule
SharedTestingModule
],
declarations: [TotalReplicationPageComponent],
providers: [
TranslateService,
{ provide: SessionService, useValue: mockSessionService },
{ provide: AppConfigService, useValue: mockAppConfigService },
{ provide: Router, useValue: mockRouter },

View File

@ -11,23 +11,59 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } from '@angular/core';
import {Router, ActivatedRoute} from "@angular/router";
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Router, ActivatedRoute, NavigationEnd } from "@angular/router";
import {SessionService} from "../../../shared/services/session.service";
import {AppConfigService} from "../../../services/app-config.service";
import { ReplicationRule } from "../../../shared/services";
import { Subscription } from "rxjs";
import { EventService, HarborEvent } from "../../../services/event-service/event.service";
// The route path which will display this component
const URL_TO_DISPLAY: string = '/harbor/replications';
@Component({
selector: 'total-replication',
templateUrl: 'total-replication-page.component.html',
styleUrls: [ './total-replication-page.component.scss' ]
})
export class TotalReplicationPageComponent {
export class TotalReplicationPageComponent implements OnInit, OnDestroy {
routerSub: Subscription;
scrollSub: Subscription;
scrollTop: number;
constructor(private router: Router,
private session: SessionService,
private appConfigService: AppConfigService,
private activeRoute: ActivatedRoute) {}
private activeRoute: ActivatedRoute,
private event: EventService) {}
ngOnInit(): void {
if (!this.scrollSub) {
this.scrollSub = this.event.subscribe(HarborEvent.SCROLL, v => {
if (v && URL_TO_DISPLAY === v.url) {
this.scrollTop = v.scrollTop;
}
});
}
if (!this.routerSub) {
this.routerSub = this.router.events.subscribe(e => {
if (e instanceof NavigationEnd) {
if (e && URL_TO_DISPLAY === e.url) { // Into view
this.event.publish(HarborEvent.SCROLL_TO_POSITION, this.scrollTop);
} else {
this.event.publish(HarborEvent.SCROLL_TO_POSITION, 0);
}
}
});
}
}
ngOnDestroy(): void {
if (this.routerSub) {
this.routerSub.unsubscribe();
this.routerSub = null;
}
if (this.scrollSub) {
this.scrollSub.unsubscribe();
this.scrollSub = null;
}
}
customRedirect(rule: ReplicationRule): void {
if (rule) {
this.router.navigate(['../projects', rule.projects[0].project_id, 'replications'], { relativeTo: this.activeRoute });

View File

@ -5,16 +5,24 @@ import { TaskListComponent } from "./task-list/task-list.component";
import { PolicyComponent } from "./policy/policy.component";
import { AddP2pPolicyComponent } from "./add-p2p-policy/add-p2p-policy.component";
import { P2pProviderService } from "./p2p-provider.service";
import { RouteConfigId } from "../../../route-reuse-strategy/harbor-route-reuse-strategy";
const routes: Routes = [
{
path: 'policies',
component: PolicyComponent
component: PolicyComponent,
data : {
reuse: true,
routeConfigId: RouteConfigId.P2P_POLICIES_PAGE
}
},
{
path: ':preheatPolicyName/executions/:executionId/tasks',
component: TaskListComponent
component: TaskListComponent,
data : {
routeConfigId: RouteConfigId.P2P_TASKS_PAGE
}
},
{ path: '', redirectTo: 'policies', pathMatch: 'full' }
];

View File

@ -14,7 +14,7 @@
import { debounceTime, finalize, switchMap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
import { Project } from '../../project';
import { clone, CustomComparator, DEFAULT_PAGE_SIZE } from '../../../../shared/units/utils';
@ -24,7 +24,7 @@ import {
UserPermissionService,
USERSTATICPERMISSION
} from '../../../../shared/services';
import {ClrDatagridStateInterface, ClrLoadingState} from '@clr/angular';
import { ClrDatagridStateInterface, ClrLoadingState } from '@clr/angular';
import {
EXECUTION_STATUS,
FILTER_TYPE,
@ -43,7 +43,9 @@ import { ProviderUnderProject } from '../../../../../../ng-swagger-gen/models/pr
import { ConfirmationDialogComponent } from "../../../../shared/components/confirmation-dialog";
import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../../../shared/entities/shared.const";
import { ConfirmationMessage } from "../../../global-confirmation-dialog/confirmation-message";
import { EventService, HarborEvent } from "../../../../services/event-service/event.service";
// The route path which will display this component
const URL_TO_DISPLAY: RegExp = /\/harbor\/projects\/(\d+)\/p2p-provider\/policies/;
@Component({
templateUrl: './policy.component.html',
styleUrls: ['./policy.component.scss']
@ -82,6 +84,9 @@ export class PolicyComponent implements OnInit, OnDestroy {
severity_map: any = PROJECT_SEVERITY_LEVEL_TO_TEXT_MAP;
timeout: any;
hasAddModalInit: boolean = false;
routerSub: Subscription;
scrollSub: Subscription;
scrollTop: number;
constructor(
private route: ActivatedRoute,
private router: Router,
@ -89,9 +94,29 @@ export class PolicyComponent implements OnInit, OnDestroy {
private p2pProviderService: P2pProviderService,
private messageHandlerService: MessageHandlerService,
private userPermissionService: UserPermissionService,
private preheatService: PreheatService) { }
private preheatService: PreheatService,
private event: EventService) {
}
ngOnInit() {
if (!this.scrollSub) {
this.scrollSub = this.event.subscribe(HarborEvent.SCROLL, v => {
if (v && URL_TO_DISPLAY.test(v.url)) {
this.scrollTop = v.scrollTop;
}
});
}
if (!this.routerSub) {
this.routerSub = this.router.events.subscribe(e => {
if (e instanceof NavigationEnd) {
if (e && URL_TO_DISPLAY.test(e.url)) { // Into view
this.event.publish(HarborEvent.SCROLL_TO_POSITION, this.scrollTop);
} else {
this.event.publish(HarborEvent.SCROLL_TO_POSITION, 0);
}
}
});
}
this.subscribeSearch();
this.projectId = +this.route.snapshot.parent.parent.params['id'];
const resolverData = this.route.snapshot.parent.parent.data;
@ -102,16 +127,26 @@ export class PolicyComponent implements OnInit, OnDestroy {
this.getPermissions();
this.refresh();
}
ngOnDestroy(): void {
if (this._searchSubscription) {
this._searchSubscription.unsubscribe();
this._searchSubscription = null;
}
this.clearLoop();
if (this.routerSub) {
this.routerSub.unsubscribe();
this.routerSub = null;
}
if (this.scrollSub) {
this.scrollSub.unsubscribe();
this.scrollSub = null;
}
if (this._searchSubscription) {
this._searchSubscription.unsubscribe();
this._searchSubscription = null;
}
this.clearLoop();
}
addModalInit() {
this.hasAddModalInit = true;
this.hasAddModalInit = true;
}
getPermissions() {
const permissionsList: Observable<boolean>[] = [];
permissionsList.push(this.userPermissionService.getPermission(this.projectId,
@ -132,6 +167,7 @@ export class PolicyComponent implements OnInit, OnDestroy {
this.addBtnState = ClrLoadingState.ERROR;
});
}
getProviders() {
this.preheatService.ListProvidersUnderProject({projectName: this.projectName})
.subscribe(res => {
@ -142,10 +178,12 @@ export class PolicyComponent implements OnInit, OnDestroy {
}
});
}
refresh() {
this.selectedRow = null;
this.getPolicies();
}
getPolicies() {
this.loading = true;
this.preheatService.ListPolicies({projectName: this.projectName})
@ -159,7 +197,8 @@ export class PolicyComponent implements OnInit, OnDestroy {
}
);
}
switchStatus() {
switchStatus() {
let content = '';
this.translate.get(
!this.selectedRow.enabled
@ -178,8 +217,9 @@ export class PolicyComponent implements OnInit, OnDestroy {
this.confirmationDialogComponent.open(message);
});
}
confirmSwitch(message) {
if (message && message.source === ConfirmationTargets.P2P_PROVIDER_STOP &&
if (message && message.source === ConfirmationTargets.P2P_PROVIDER_STOP &&
message.state === ConfirmationState.CONFIRMED) {
this.stopLoading = true;
const execution: Execution = clone(this.selectedExecutionRow);
@ -196,7 +236,7 @@ export class PolicyComponent implements OnInit, OnDestroy {
this.messageHandlerService.error(error);
});
}
if (message && message.source === ConfirmationTargets.P2P_PROVIDER_EXECUTE &&
if (message && message.source === ConfirmationTargets.P2P_PROVIDER_EXECUTE &&
message.state === ConfirmationState.CONFIRMED) {
this.executing = true;
this.preheatService.ManualPreheat({
@ -220,16 +260,16 @@ export class PolicyComponent implements OnInit, OnDestroy {
this.preheatService.UpdatePolicy({
projectName: this.projectName,
preheatPolicyName: this.selectedRow.name,
policy: Object.assign({}, this.selectedRow, { enabled: !this.selectedRow.enabled })
policy: Object.assign({}, this.selectedRow, {enabled: !this.selectedRow.enabled})
}).subscribe(
response => {
this.messageHandlerService.showSuccess('P2P_PROVIDER.UPDATED_SUCCESSFULLY');
this.refresh();
},
error => {
this.messageHandlerService.handleError(error);
}
);
response => {
this.messageHandlerService.showSuccess('P2P_PROVIDER.UPDATED_SUCCESSFULLY');
this.refresh();
},
error => {
this.messageHandlerService.handleError(error);
}
);
}
}
if (message &&
@ -251,11 +291,13 @@ export class PolicyComponent implements OnInit, OnDestroy {
);
}
}
newPolicy() {
this.addP2pPolicyComponent.isOpen = true;
this.addP2pPolicyComponent.isEdit = false;
this.addP2pPolicyComponent.resetForAdd();
}
editPolicy() {
if (this.selectedRow) {
this.addP2pPolicyComponent.repos = null;
@ -269,25 +311,25 @@ export class PolicyComponent implements OnInit, OnDestroy {
if (filter && filter.length) {
filter.forEach(item => {
if (item.type === FILTER_TYPE.REPOS && item.value) {
let str: string = item.value;
if (/^{\S+}$/.test(str)) {
return str.slice(1, str.length - 1);
}
this.addP2pPolicyComponent.repos = str;
let str: string = item.value;
if (/^{\S+}$/.test(str)) {
return str.slice(1, str.length - 1);
}
this.addP2pPolicyComponent.repos = str;
}
if (item.type === FILTER_TYPE.TAG && item.value) {
let str: string = item.value;
if (/^{\S+}$/.test(str)) {
return str.slice(1, str.length - 1);
}
this.addP2pPolicyComponent.tags = str;
let str: string = item.value;
if (/^{\S+}$/.test(str)) {
return str.slice(1, str.length - 1);
}
this.addP2pPolicyComponent.tags = str;
}
if (item.type === FILTER_TYPE.LABEL && item.value) {
let str: string = item.value;
if (/^{\S+}$/.test(str)) {
return str.slice(1, str.length - 1);
}
this.addP2pPolicyComponent.labels = str;
let str: string = item.value;
if (/^{\S+}$/.test(str)) {
return str.slice(1, str.length - 1);
}
this.addP2pPolicyComponent.labels = str;
}
});
}
@ -317,13 +359,14 @@ export class PolicyComponent implements OnInit, OnDestroy {
this.addP2pPolicyComponent.originCronForEdit = this.addP2pPolicyComponent.cron;
}
}
deletePolicy() {
const names: string[] = [];
names.push(this.selectedRow.name);
let content = '';
this.translate.get(
'P2P_PROVIDER.DELETE_POLICY_SUMMARY'
, {names: names.join(',')}).subscribe((res) => content = res);
'P2P_PROVIDER.DELETE_POLICY_SUMMARY'
, {names: names.join(',')}).subscribe((res) => content = res);
const msg: ConfirmationMessage = new ConfirmationMessage(
"SCANNER.CONFIRM_DELETION",
content,
@ -334,8 +377,9 @@ export class PolicyComponent implements OnInit, OnDestroy {
);
this.confirmationDialogComponent.open(msg);
}
executePolicy() {
if (this.selectedRow && this.selectedRow.enabled) {
if (this.selectedRow && this.selectedRow.enabled) {
const message = new ConfirmationMessage(
"P2P_PROVIDER.EXECUTE_TITLE",
"P2P_PROVIDER.EXECUTE_SUMMARY",
@ -347,6 +391,7 @@ export class PolicyComponent implements OnInit, OnDestroy {
this.confirmationDialogComponent.open(message);
}
}
success(isAdd: boolean) {
let message: string;
if (isAdd) {
@ -357,10 +402,11 @@ export class PolicyComponent implements OnInit, OnDestroy {
this.messageHandlerService.showSuccess(message);
this.refresh();
}
clrLoadJobs(chosenPolicy: PreheatPolicy, withLoading: boolean, state?: ClrDatagridStateInterface) {
if (this.selectedRow) {
if (state && state.page) {
this.pageSize = state.page.size;
this.pageSize = state.page.size;
}
if (withLoading) {
// if datagrid is under control of *ngIf, should add timeout in case of ng changes checking error
@ -370,7 +416,7 @@ export class PolicyComponent implements OnInit, OnDestroy {
}
let params: string;
if (this.searchString) {
params = encodeURIComponent(`${this.filterKey}=~${this.searchString}`);
params = encodeURIComponent(`${this.filterKey}=~${this.searchString}`);
}
this.preheatService.ListExecutionsResponse({
projectName: this.projectName,
@ -393,7 +439,8 @@ export class PolicyComponent implements OnInit, OnDestroy {
});
}
}
refreshJobs (chosenPolicy?: PreheatPolicy) {
refreshJobs(chosenPolicy?: PreheatPolicy) {
this.executionList = [];
this.currentExecutionPage = 1;
this.totalExecutionCount = 0;
@ -401,6 +448,7 @@ export class PolicyComponent implements OnInit, OnDestroy {
this.searchString = null;
this.clrLoadJobs(chosenPolicy, true);
}
openStopExecutionsDialog() {
if (this.selectedExecutionRow) {
const stopExecutionsMessage = new ConfirmationMessage(
@ -414,59 +462,70 @@ export class PolicyComponent implements OnInit, OnDestroy {
this.confirmationDialogComponent.open(stopExecutionsMessage);
}
}
goToLink(executionId: number) {
const linkUrl = ["harbor",
"projects", `${this.projectId}`, "p2p-provider", `${this.selectedRow.name}`, "executions", `${executionId}`, "tasks"];
this.router.navigate(linkUrl);
}
getTriggerTypeI18n(trigger: string): string {
if (JSON.parse(trigger).type) {
return TRIGGER_I18N_MAP[JSON.parse(trigger).type];
}
return TRIGGER_I18N_MAP[TRIGGER.MANUAL];
}
getTriggerTypeI18nForExecution(trigger: string) {
if (trigger && TRIGGER_I18N_MAP[trigger]) {
return TRIGGER_I18N_MAP[trigger];
}
return trigger;
}
isScheduled(trigger: string): boolean {
return JSON.parse(trigger).type === TRIGGER.SCHEDULED;
}
isEventBased(trigger: string): boolean {
return JSON.parse(trigger).type === TRIGGER.EVENT_BASED;
}
getScheduledCron(trigger: string): string {
return JSON.parse(trigger).trigger_setting.cron;
}
getDuration(e: Execution): string {
return this.p2pProviderService.getDuration(e.start_time, e.end_time);
}
getValue(filter: string, type: string): string {
const arr: any[] = JSON.parse(filter);
if (arr && arr.length) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].type === type && arr[i].value) {
let str: string = arr[i].value;
if (/^{\S+}$/.test(str)) {
return str.slice(1, str.length - 1);
}
return str;
let str: string = arr[i].value;
if (/^{\S+}$/.test(str)) {
return str.slice(1, str.length - 1);
}
return str;
}
}
}
return "";
}
getSuccessRate(m: Metrics): number {
if (m && m.task_count && m.success_task_count) {
return m.success_task_count / m.task_count;
}
return 0;
}
selectFilterKey($event: any): void {
this.filterKey = $event['target'].value;
}
openFilter(isOpen: boolean): void {
this.isOpenFilterTag = isOpen;
}
@ -479,6 +538,7 @@ export class PolicyComponent implements OnInit, OnDestroy {
this.clrLoadJobs(null, true);
}
}
subscribeSearch() {
if (!this._searchSubscription) {
this._searchSubscription = this._searchSubject.pipe(
@ -497,26 +557,29 @@ export class PolicyComponent implements OnInit, OnDestroy {
q: params
}).pipe(finalize(() => this.jobsLoading = false));
})).subscribe(response => {
if (response.headers) {
let xHeader: string = response.headers.get('x-total-count');
if (xHeader) {
this.totalExecutionCount = parseInt(xHeader, 0);
}
}
this.executionList = response.body;
this.setLoop();
if (response.headers) {
let xHeader: string = response.headers.get('x-total-count');
if (xHeader) {
this.totalExecutionCount = parseInt(xHeader, 0);
}
}
this.executionList = response.body;
this.setLoop();
});
}
}
canStop(): boolean {
return this.selectedExecutionRow && this.p2pProviderService.willChangStatus(this.selectedExecutionRow.status);
}
clearLoop() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
}
setLoop() {
this.clearLoop();
if (this.executionList && this.executionList.length) {

View File

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
import {PreloadAllModules, RouteReuseStrategy, RouterModule, Routes} from '@angular/router';
import { AuthCheckGuard } from './shared/router-guard/auth-user-activate.service';
import { SignInGuard } from './shared/router-guard/sign-in-guard-activate.service';
import { OidcGuard } from './shared/router-guard/oidc-guard-active.service';
import {HarborRouteReuseStrategy} from "./route-reuse-strategy/harbor-route-reuse-strategy";
const harborRoutes: Routes = [
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
@ -48,6 +49,9 @@ const harborRoutes: Routes = [
];
@NgModule({
providers: [
{ provide: RouteReuseStrategy, useClass: HarborRouteReuseStrategy }
],
imports: [
RouterModule.forRoot(harborRoutes, {
onSameUrlNavigation: 'reload',

View File

@ -0,0 +1,17 @@
import { HarborRouteReuseStrategy } from "./harbor-route-reuse-strategy";
import { ActivatedRouteSnapshot } from "@angular/router";
describe('HarborRouteReuseStrategy', () => {
let harborRouteReuseStrategy: HarborRouteReuseStrategy;
beforeEach(() => {
harborRouteReuseStrategy = new HarborRouteReuseStrategy();
});
it('should be created', () => {
expect(harborRouteReuseStrategy).toBeTruthy();
});
it('shouldReuseRoute', () => {
const future: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
const curr: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
expect(harborRouteReuseStrategy.shouldReuseRoute(future, curr)).toBeTruthy();
});
});

View File

@ -0,0 +1,110 @@
import { RouteReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle } from '@angular/router';
/**
* if want to reuse a route, add reuse: true to its routeConfig data as below:
* data : {
* reuse: true,
* routeConfigId: 'one unique id'
* }
*/
export enum RouteConfigId {
REPLICATION_PAGE = 'TotalReplicationPageComponent',
REPLICATION_TASKS_PAGE = 'ReplicationTasksComponent',
P2P_POLICIES_PAGE = 'PolicyComponent',
P2P_TASKS_PAGE = 'P2pTaskListComponent'
}
export class HarborRouteReuseStrategy implements RouteReuseStrategy {
/**
* 1.for each routing action, cache will be removed by default
* 2.add the routing actions here that should keep cache
* 3.you need to add routeConfigId: 'one unique id' to the related router configs like below:
* data : {
* reuse: true,
* routeConfigId: 'one unique id'
* }
* @param future
* @param curr
* @private
*/
private shouldKeepCache(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) {
if (future.routeConfig && curr.routeConfig && future.routeConfig.data && curr.routeConfig.data) {
// action 1: from replication tasks list page to TotalReplicationPageComponent page
if (future.routeConfig.data.routeConfigId === RouteConfigId.REPLICATION_TASKS_PAGE
&& curr.routeConfig.data.routeConfigId === RouteConfigId.REPLICATION_PAGE) {
this.shouldDeleteCache = false;
}
// action 2: from preheat tasks list page to PolicyComponent page
if (future.routeConfig.data.routeConfigId === RouteConfigId.P2P_TASKS_PAGE
&& curr.routeConfig.data.routeConfigId === RouteConfigId.P2P_POLICIES_PAGE) {
this.shouldDeleteCache = false;
}
}
}
private _cache: { [key: string]: DetachedRouteHandle } = {};
// cache will be removed by default
private shouldDeleteCache: boolean = true;
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
this.shouldKeepCache(future, curr);
return future.routeConfig === curr.routeConfig;
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
if (this.isReuseRoute(route)) {
if (this.shouldDeleteCache) {
this.clearAllCache();
}
}
this.shouldDeleteCache = true;
return this._cache[this.getFullUrl(route)] && this.isReuseRoute(route);
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
if (this._cache[this.getFullUrl(route)] && this.isReuseRoute(route)) {
return this._cache[this.getFullUrl(route)];
}
return null;
}
shouldDetach(route: ActivatedRouteSnapshot): boolean {
return this.isReuseRoute(route);
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
// use the full urls as cache keys
this._cache[this.getFullUrl(route)] = handle;
}
// full url, equals to window.location.pathName
private getFullUrl(route: ActivatedRouteSnapshot) {
return route['_routerState'].url;
}
// if this route should be reused
private isReuseRoute(route: ActivatedRouteSnapshot): boolean {
return route && route.routeConfig && route.routeConfig.data && route.routeConfig.data.reuse;
}
// clear cache
private clearAllCache() {
for (let name in this._cache) {
if (this._cache.hasOwnProperty(name)) {
if (this._cache[name]) {
if ((this._cache[name] as any).componentRef) {
(this._cache[name] as any).componentRef.destroy(); // manually call destroy(), to destroy component
}
}
delete this._cache[name];
}
}
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { EventService } from "./event.service";
describe('EventServiceService', () => {
let service: EventService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(EventService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,60 @@
import { Injectable } from '@angular/core';
import { from, Subject } from "rxjs";
@Injectable({
providedIn: "root"
})
export class EventService {
private listeners = {};
private eventsSubject = new Subject();
private events = from(this.eventsSubject);
constructor() {
this.events.subscribe(
({name, args}) => {
if (this.listeners[name]) {
for (let listener of this.listeners[name]) {
listener(...args);
}
}
});
}
subscribe(name: string, listener): any {
if (!this.listeners[name]) {
this.listeners[name] = [];
}
this.listeners[name].push(listener);
return {
unsubscribe: () => {
this.doUnsubscribe(name, listener);
}
};
}
doUnsubscribe(name, listener) {
this.listeners[name] = this.listeners[name].filter((v) => {
return v !== listener;
});
}
unsubscribe(name, listener?) {
if (this.listeners[name]) {
if (!listener) {
this.listeners[name] = [];
} else {
this.doUnsubscribe(name, listener);
}
}
}
publish(name, ...args) {
this.eventsSubject.next({
name,
args
});
}
}
export enum HarborEvent {
SCROLL = 'scroll',
SCROLL_TO_POSITION = 'scrollToPosition'
}