RIC Dashboard Release Notes
===========================
-Version 2.0.0, 22 Dec 2019
+Version 2.0.0, 20 Dec 2019
--------------------------
* Change application properties file to YAML format
* Add mock list of RIC instances to application properties
* Drop AUX cluster container list fetched via CAAS Ingress
* Revise controller methods to require RIC instance key
* Upgrade to Spring-Boot 2.1.11.RELEASE
+* Add instance-selector service
+* Revise ac-xapp, app-mgr, caas-ingress, e2-mgr service to support multiple ric instances
+* Revise ac-xapp, app-control, catalog, caas-ingress, ran-control components to listen to the instance change
Version 1.3.0, 26 Nov 2019
--------------------------
* ========================LICENSE_END===================================
*/
-import { Component, OnInit } from '@angular/core';
-import { FormGroup, FormControl, Validators } from '@angular/forms';
-import { ACAdmissionIntervalControl, ACAdmissionIntervalControlAck } from '../interfaces/ac-xapp.types';
+import { HttpErrorResponse } from '@angular/common/http';
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { ACAdmissionIntervalControl } from '../interfaces/ac-xapp.types';
import { ACXappService } from '../services/ac-xapp/ac-xapp.service';
import { ErrorDialogService } from '../services/ui/error-dialog.service';
import { NotificationService } from './../services/ui/notification.service';
-import { HttpErrorResponse } from '@angular/common/http';
+import { Subscription } from 'rxjs';
+import { InstanceSelectorService } from '../services/instance-selector/instance-selector.service';
@Component({
selector: 'rd-ac-xapp',
export class AcXappComponent implements OnInit {
private acForm: FormGroup;
+ private instanceChange: Subscription;
+ private instanceKey: string;
constructor(
private acXappService: ACXappService,
private errorDialogService: ErrorDialogService,
- private notificationService: NotificationService) { }
+ private notificationService: NotificationService,
+ public instanceSelectorService: InstanceSelectorService, ) { }
ngOnInit() {
const windowLengthPattern = /^([0-9]{1}|[1-5][0-9]{1}|60)$/;
const blockingRatePattern = /^([0-9]{1,2}|100)$/;
const triggerPattern = /^([0-9]+)$/;
+
this.acForm = new FormGroup({
// Names must match the ACAdmissionIntervalControl interface
enforce: new FormControl(true, [Validators.required]),
blocking_rate: new FormControl('', [Validators.required, Validators.pattern(blockingRatePattern)]),
trigger_threshold: new FormControl('', [Validators.required, Validators.pattern(triggerPattern)])
});
- // TODO: show pending action indicator
- this.acXappService.getPolicy().subscribe((res: ACAdmissionIntervalControl) => {
- this.acForm.controls['enforce'].setValue(res.enforce);
- this.acForm.controls['window_length'].setValue(res.window_length);
- this.acForm.controls['blocking_rate'].setValue(res.blocking_rate);
- this.acForm.controls['trigger_threshold'].setValue(res.trigger_threshold);
- // TODO: clear pending action indicator
- },
- (error: HttpErrorResponse) => {
- // TODO: clear pending action indicator
- this.errorDialogService.displayError(error.message);
- });
+
+ this.instanceChange = this.instanceSelectorService.getSelectedInstancekey().subscribe((instanceKey: string) => {
+ if (instanceKey) {
+ // TODO: show pending action indicator
+ this.instanceKey = instanceKey;
+ this.acXappService.getPolicy(instanceKey).subscribe((res: ACAdmissionIntervalControl) => {
+ this.acForm.controls['enforce'].setValue(res.enforce);
+ this.acForm.controls['window_length'].setValue(res.window_length);
+ this.acForm.controls['blocking_rate'].setValue(res.blocking_rate);
+ this.acForm.controls['trigger_threshold'].setValue(res.trigger_threshold);
+ // TODO: clear pending action indicator
+ },
+ (error: HttpErrorResponse) => {
+ // TODO: clear pending action indicator
+ this.errorDialogService.displayError(error.message);
+ });
+ }
+ })
+ }
+
+ ngOnDestroy() {
+ this.instanceChange.unsubscribe();
}
updateAc = (acFormValue: ACAdmissionIntervalControl) => {
blocking_rate: +acFormValue.blocking_rate,
trigger_threshold: +acFormValue.trigger_threshold
};
- this.acXappService.putPolicy(acFormValueConverted).subscribe(
+ this.acXappService.putPolicy(this.instanceKey, acFormValueConverted).subscribe(
response => {
if (response.status === 200) {
this.notificationService.success('AC update policy succeeded!');
-->
<div mat-dialog-title *ngIf="loading$ | async">
- Loading {{data.name}} Configuration
+ Loading {{data.xapp.name}} Configuration
<mat-spinner diameter=70></mat-spinner>
</div>
<div *ngIf="((loading$ | async)==false) " class="config-div">
<div mat-dialog-title class="config-title">
- {{data.name}} Configuration
+ {{data.xapp.name}} Configuration
</div>
<div class="config-form">
xappLayout: any;
ngOnInit() {
this.loadingSubject.next(true);
- this.appMgrService.getConfig()
+ this.appMgrService.getConfig(this.data.instanceKey)
.pipe(
finalize(() => this.loadingSubject.next(false))
)
.subscribe(
(allConfig: any) => {
- this.loadConfigForm(this.data.name, allConfig)
+ this.loadConfigForm(this.data.xapp.name, allConfig)
}
);
}
config: event,
layout: this.xappLayout
}
- this.loadingDialogService.startLoading("Updating " + this.data.name + " configuration");
- this.appMgrService.putConfig(config)
+ this.loadingDialogService.startLoading("Updating " + this.data.xapp.name + " configuration");
+ this.appMgrService.putConfig(this.data.instanceKey, config)
.pipe(
finalize(() => {
this.loadingDialogService.stopLoading();
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatSort } from '@angular/material/sort';
import { Router } from '@angular/router';
+import { Subscription } from 'rxjs';
+import { finalize } from 'rxjs/operators';
import { XappControlRow } from '../interfaces/app-mgr.types';
import { AppMgrService } from '../services/app-mgr/app-mgr.service';
+import { InstanceSelectorService } from '../services/instance-selector/instance-selector.service';
import { ConfirmDialogService } from '../services/ui/confirm-dialog.service';
import { ErrorDialogService } from '../services/ui/error-dialog.service';
import { LoadingDialogService } from '../services/ui/loading-dialog.service';
import { NotificationService } from '../services/ui/notification.service';
import { AppControlAnimations } from './app-control.animations';
import { AppControlDataSource } from './app-control.datasource';
-import { finalize } from 'rxjs/operators';
@Component({
selector: 'rd-app-control',
displayedColumns: string[] = ['xapp', 'name', 'status', 'ip', 'port', 'action'];
dataSource: AppControlDataSource;
- @ViewChild(MatSort, {static: true}) sort: MatSort;
+ @ViewChild(MatSort, { static: true }) sort: MatSort;
+ private instanceChange: Subscription;
+ private instanceKey: string;
constructor(
private appMgrSvc: AppMgrService,
private confirmDialogService: ConfirmDialogService,
private errorDialogService: ErrorDialogService,
private loadingDialogService: LoadingDialogService,
+ public instanceSelectorService: InstanceSelectorService,
private notificationService: NotificationService) { }
+
ngOnInit() {
this.dataSource = new AppControlDataSource(this.appMgrSvc, this.sort, this.notificationService);
- this.dataSource.loadTable();
+ this.instanceChange = this.instanceSelectorService.getSelectedInstancekey().subscribe((instanceKey: string) => {
+ if (instanceKey) {
+ this.instanceKey = instanceKey;
+ this.dataSource.loadTable(instanceKey);
+ }
+ })
+ }
+
+ ngOnDestroy() {
+ this.instanceChange.unsubscribe();
}
controlApp(app: XappControlRow): void {
// TODO: identify apps without hardcoding to names
- const acAppPattern0 = /[Aa][Dd][Mm][Ii][Nn]/;
- const acAppPattern1 = /[Aa][Dd][Mm][Ii][Ss]{2}[Ii][Oo][Nn]/;
+ const acAppPattern0 = /[Aa][Dd][Mm][Ii][Nn]/;
+ const acAppPattern1 = /[Aa][Dd][Mm][Ii][Ss]{2}[Ii][Oo][Nn]/;
if (acAppPattern0.test(app.xapp) || acAppPattern1.test(app.xapp)) {
this.router.navigate(['/ac']);
- } else {
+ } else {
this.errorDialogService.displayError('No control available for ' + app.xapp + ' (yet)');
}
}
onUndeployApp(app: XappControlRow): void {
this.confirmDialogService.openConfirmDialog('Are you sure you want to undeploy App ' + app.xapp + '?')
- .afterClosed().subscribe( (res: boolean) => {
+ .afterClosed().subscribe((res: boolean) => {
if (res) {
this.loadingDialogService.startLoading("Undeploying " + app.xapp);
- this.appMgrSvc.undeployXapp(app.xapp)
+ this.appMgrSvc.undeployXapp(this.instanceKey, app.xapp)
.pipe(
finalize(() => this.loadingDialogService.stopLoading())
)
.subscribe(
- ( httpResponse: HttpResponse<Object>) => {
- // Answers 204/No content on success
- this.notificationService.success('App undeployed successfully!');
- this.dataSource.loadTable();
- },
- ( (her: HttpErrorResponse) => {
- // the error field should have an ErrorTransport object
- let msg = her.message;
- if (her.error && her.error.message) {
- msg = her.error.message;
- }
- this.notificationService.warn('App undeploy failed: ' + msg);
- })
- );
+ (httpResponse: HttpResponse<Object>) => {
+ // Answers 204/No content on success
+ this.notificationService.success('App undeployed successfully!');
+ this.dataSource.loadTable(this.instanceKey);
+ },
+ ((her: HttpErrorResponse) => {
+ // the error field should have an ErrorTransport object
+ let msg = her.message;
+ if (her.error && her.error.message) {
+ msg = her.error.message;
+ }
+ this.notificationService.warn('App undeploy failed: ' + msg);
+ })
+ );
}
});
}
public rowCount = 1; // hide footer during intial load
private emptyInstances: XMXappInstance =
- { ip: null,
+ {
+ ip: null,
name: null,
port: null,
status: null,
super();
}
- loadTable() {
+ loadTable(instanceKey: string) {
this.loadingSubject.next(true);
- this.appMgrSvc.getDeployed()
+ this.appMgrSvc.getDeployed(instanceKey)
.pipe(
- catchError( (her: HttpErrorResponse) => {
+ catchError((her: HttpErrorResponse) => {
console.log('AppControlDataSource failed: ' + her.message);
this.notificationService.error('Failed to get applications: ' + her.message);
return of([]);
}),
finalize(() => this.loadingSubject.next(false))
)
- .subscribe( (xApps: XMDeployedApp[]) => {
+ .subscribe((xApps: XMDeployedApp[]) => {
this.rowCount = xApps.length;
const flattenedApps = this.flatten(xApps);
this.appControlSubject.next(flattenedApps);
this.loadingSubject.complete();
}
- private flatten(allxappdata: XMDeployedApp[]): XappControlRow[] {
+ private flatten(allxappdata: XMDeployedApp[]): XappControlRow[] {
const xAppInstances: XappControlRow[] = [];
for (const xapp of allxappdata) {
if (!xapp.instances) {
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { MatSort } from '@angular/material/sort';
-import { CaasIngressDataSource } from './caas-ingress.datasource';
+import { Subscription } from 'rxjs';
import { CaasIngressService } from '../services/caas-ingress/caas-ingress.service';
+import { InstanceSelectorService } from '../services/instance-selector/instance-selector.service';
import { ConfirmDialogService } from '../services/ui/confirm-dialog.service';
import { ErrorDialogService } from '../services/ui/error-dialog.service';
import { LoadingDialogService } from '../services/ui/loading-dialog.service';
import { NotificationService } from '../services/ui/notification.service';
+import { CaasIngressDataSource } from './caas-ingress.datasource';
@Component({
selector: 'rd-caas-ingress',
@Input() namespace: string;
dataSource: CaasIngressDataSource;
- displayedColumns: string[] = [ 'namespace', 'name', 'status', 'ip', 'containers', 'restartCount', 'creationTime' ];
+ displayedColumns: string[] = ['namespace', 'name', 'status', 'ip', 'containers', 'restartCount', 'creationTime'];
@ViewChild(MatSort, { static: true }) sort: MatSort;
+ private instanceChange: Subscription;
constructor(
private caasIngressSvc: CaasIngressService,
private confirmDialogService: ConfirmDialogService,
private errorDialogService: ErrorDialogService,
private loadingDialogService: LoadingDialogService,
+ public instanceSelectorService: InstanceSelectorService,
private notificationService: NotificationService) { }
ngOnInit() {
this.dataSource = new CaasIngressDataSource(this.caasIngressSvc, this.sort, this.notificationService);
- this.dataSource.loadTable(this.cluster, this.namespace);
+ this.instanceChange = this.instanceSelectorService.getSelectedInstancekey().subscribe((instanceKey: string) => {
+ if (instanceKey) {
+ this.dataSource.loadTable(instanceKey, this.cluster, this.namespace);
+ }
+ })
+ }
+
+ ngOnDestroy() {
+ this.instanceChange.unsubscribe();
}
}
super();
}
- loadTable(cluster: string, namespace: string) {
+ loadTable(instanceKey: string, cluster: string, namespace: string) {
this.loadingSubject.next(true);
- this.caasIngressService.getPodList(cluster, namespace)
+ this.caasIngressService.getPodList(instanceKey, cluster, namespace)
.pipe(
catchError((her: HttpErrorResponse) => {
console.log('CaasIngressDataSource failed: ' + her.message);
* ========================LICENSE_END===================================
*/
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
-import { Component, OnInit, ViewChild } from '@angular/core';
+import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSort } from '@angular/material/sort';
+import { Subscription } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { XMDeployableApp } from '../interfaces/app-mgr.types';
import { AppMgrService } from '../services/app-mgr/app-mgr.service';
-import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { InstanceSelectorService } from '../services/instance-selector/instance-selector.service';
import { LoadingDialogService } from '../services/ui/loading-dialog.service';
+import { UiService } from '../services/ui/ui.service';
import { AppConfigurationComponent } from './../app-configuration/app-configuration.component';
import { ConfirmDialogService } from './../services/ui/confirm-dialog.service';
import { NotificationService } from './../services/ui/notification.service';
import { CatalogDataSource } from './catalog.datasource';
-import { UiService } from '../services/ui/ui.service';
@Component({
selector: 'rd-app-catalog',
templateUrl: './catalog.component.html',
styleUrls: ['./catalog.component.scss'],
})
-export class CatalogComponent implements OnInit {
+export class CatalogComponent implements OnInit, OnDestroy {
darkMode: boolean;
panelClass: string = "";
displayedColumns: string[] = ['name', 'version', 'action'];
dataSource: CatalogDataSource;
+ private instanceChange: Subscription;
+ private instanceKey: string;
+
@ViewChild(MatSort, { static: true }) sort: MatSort;
constructor(
private appMgrService: AppMgrService,
private confirmDialogService: ConfirmDialogService,
private dialog: MatDialog,
- private errorDiaglogService: ErrorDialogService,
private loadingDialogService: LoadingDialogService,
private notificationService: NotificationService,
+ public instanceSelectorService: InstanceSelectorService,
public ui: UiService) { }
ngOnInit() {
this.dataSource = new CatalogDataSource(this.appMgrService, this.sort, this.notificationService);
- this.dataSource.loadTable();
this.ui.darkModeState.subscribe((isDark) => {
this.darkMode = isDark;
});
+
+ this.instanceChange = this.instanceSelectorService.getSelectedInstancekey().subscribe((instanceKey: string) => {
+ if (instanceKey) {
+ this.instanceKey = instanceKey;
+ this.dataSource.loadTable(instanceKey);
+ }
+ })
+ }
+
+ ngOnDestroy() {
+ this.instanceChange.unsubscribe();
}
onConfigureApp(xapp: XMDeployableApp): void {
position: {
top: '10%'
},
- data: xapp
+ data: {
+ xapp: xapp,
+ instanceKey: this.instanceKey
+ }
+
})
}
.afterClosed().subscribe((res: boolean) => {
if (res) {
this.loadingDialogService.startLoading('Deploying ' + app.name);
- this.appMgrService.deployXapp(app.name)
+ this.appMgrService.deployXapp(this.instanceKey, app.name)
.pipe(
finalize(() => this.loadingDialogService.stopLoading())
)
export class CatalogDataSource extends DataSource<XMDeployableApp> {
private catalogSubject = new BehaviorSubject<XMDeployableApp[]>([]);
-
private loadingSubject = new BehaviorSubject<boolean>(false);
-
public loading$ = this.loadingSubject.asObservable();
-
public rowCount = 1; // hide footer during intial load
- constructor(private appMgrSvc: AppMgrService,
+ constructor(
+ private appMgrSvc: AppMgrService,
private sort: MatSort,
private notificationService: NotificationService) {
super();
}
- loadTable() {
+ loadTable(instanceKey: string) {
this.loadingSubject.next(true);
- this.appMgrSvc.getDeployable()
+ this.appMgrSvc.getDeployable(instanceKey)
.pipe(
- catchError( (her: HttpErrorResponse) => {
+ catchError((her: HttpErrorResponse) => {
console.log('CatalogDataSource failed: ' + her.message);
this.notificationService.error('Failed to get applications: ' + her.message);
return of([]);
}),
finalize(() => this.loadingSubject.next(false))
)
- .subscribe( (xApps: XMDeployableApp[]) => {
+ .subscribe((xApps: XMDeployableApp[]) => {
this.rowCount = xApps.length;
this.catalogSubject.next(xApps);
});
active: boolean;
[position: number]: EcompRole;
}
+
+export interface RicInstance {
+ key: string;
+ name: string;
+}
\ No newline at end of file
* ========================LICENSE_END===================================
*/
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
-import { Component, OnInit } from '@angular/core';
+import { Component, Inject, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
-import { MatDialogRef } from '@angular/material/dialog';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { E2SetupRequest, RanDialogFormData } from '../interfaces/e2-mgr.types';
private service: E2ManagerService,
private errorService: ErrorDialogService,
private loadingDialogService: LoadingDialogService,
- private notifService: NotificationService) {
+ private notifService: NotificationService,
+ @Inject(MAT_DIALOG_DATA) private data) {
// opens with empty fields; accepts no data to display
}
this.loadingDialogService.startLoading('Setting up connection');
let observable: Observable<HttpResponse<Object>>;
if (ranFormValue.ranType === 'endc') {
- observable = this.service.endcSetup(setupRequest);
+ observable = this.service.endcSetup(this.data.instanceKey, setupRequest);
} else {
- observable = this.service.x2Setup(setupRequest);
+ observable = this.service.x2Setup(this.data.instanceKey, setupRequest);
}
observable
.pipe(
import { HttpErrorResponse } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
+import { Subscription } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { E2ManagerService } from '../services/e2-mgr/e2-mgr.service';
+import { InstanceSelectorService } from '../services/instance-selector/instance-selector.service';
import { ConfirmDialogService } from '../services/ui/confirm-dialog.service';
import { ErrorDialogService } from '../services/ui/error-dialog.service';
import { LoadingDialogService } from '../services/ui/loading-dialog.service';
import { NotificationService } from '../services/ui/notification.service';
+import { UiService } from '../services/ui/ui.service';
import { RanControlConnectDialogComponent } from './ran-connection-dialog.component';
import { RANControlDataSource } from './ran-control.datasource';
-import { UiService } from '../services/ui/ui.service';
@Component({
selector: 'rd-ran-control',
panelClass: string = "";
displayedColumns: string[] = ['nbId', 'nodeType', 'ranName', 'ranIp', 'ranPort', 'connectionStatus'];
dataSource: RANControlDataSource;
+ private instanceChange: Subscription;
+ private instanceKey: string;
constructor(private e2MgrSvc: E2ManagerService,
private errorDialogService: ErrorDialogService,
private confirmDialogService: ConfirmDialogService,
private notificationService: NotificationService,
private loadingDialogService: LoadingDialogService,
+ public instanceSelectorService: InstanceSelectorService,
public dialog: MatDialog,
public ui: UiService) { }
ngOnInit() {
this.dataSource = new RANControlDataSource(this.e2MgrSvc, this.notificationService);
- this.dataSource.loadTable();
+
this.ui.darkModeState.subscribe((isDark) => {
this.darkMode = isDark;
});
+
+ this.instanceChange = this.instanceSelectorService.getSelectedInstancekey().subscribe((instanceKey: string) => {
+ if (instanceKey) {
+ this.instanceKey = instanceKey;
+ this.dataSource.loadTable(instanceKey);
+ }
+ })
+ }
+
+ ngOnDestroy() {
+ this.instanceChange.unsubscribe();
}
setupRANConnection() {
}
const dialogRef = this.dialog.open(RanControlConnectDialogComponent, {
panelClass: this.panelClass,
- width: '450px'
+ width: '450px',
+ data: {
+ instanceKey: this.instanceKey
+ }
});
dialogRef.afterClosed()
.subscribe((result: boolean) => {
- if (result) {
- this.dataSource.loadTable();
- }
- });
+ if (result) {
+ this.dataSource.loadTable(this.instanceKey);
+ }
+ });
}
disconnectAllRANConnections() {
const aboutError = 'Disconnect all RAN Connections Failed: ';
this.confirmDialogService.openConfirmDialog('Are you sure you want to disconnect all RAN connections?')
- .afterClosed().subscribe( (res: boolean) => {
+ .afterClosed().subscribe((res: boolean) => {
if (res) {
this.loadingDialogService.startLoading("Disconnecting");
- this.e2MgrSvc.nodebPut()
+ this.e2MgrSvc.nodebPut(this.instanceKey)
.pipe(
finalize(() => this.loadingDialogService.stopLoading())
)
.subscribe(
- ( body: any ) => {
- this.notificationService.success('Disconnect succeeded!');
- this.dataSource.loadTable();
- },
- (her: HttpErrorResponse) => {
- // the error field should have an ErrorTransport object
- let msg = her.message;
- if (her.error && her.error.message) {
- msg = her.error.message;
+ (body: any) => {
+ this.notificationService.success('Disconnect succeeded!');
+ this.dataSource.loadTable(this.instanceKey);
+ },
+ (her: HttpErrorResponse) => {
+ // the error field should have an ErrorTransport object
+ let msg = her.message;
+ if (her.error && her.error.message) {
+ msg = her.error.message;
+ }
+ this.errorDialogService.displayError('Disconnect failed: ' + msg);
}
- this.errorDialogService.displayError('Disconnect failed: ' + msg);
- }
- );
+ );
}
});
}
super();
}
- loadTable() {
+ loadTable(instanceKey: string) {
this.loadingSubject.next(true);
- this.e2MgrSvcservice.getRan()
+ this.e2MgrSvcservice.getRan(instanceKey)
.pipe(
catchError( (her: HttpErrorResponse) => {
console.log('RANControlDataSource failed: ' + her.message);
viewBox="31.5 30 49.9 32">
<rect id="Rectangle_9" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
class="hamburger__icon__fill" data-name="Rectangle 9" rx="2"
- transform="translate(31.5 58)"/>
+ transform="translate(31.5 58)" />
<rect id="Rectangle_10" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
class="hamburger__icon__fill" data-name="Rectangle 10" rx="2"
- transform="translate(31.5 44)"/>
+ transform="translate(31.5 44)" />
<rect id="Rectangle_11" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
class="hamburger__icon__fill" data-name="Rectangle 11" rx="2"
- transform="translate(31.5 30)"/>
+ transform="translate(31.5 30)" />
</svg>
- <img src="../../assets/oran-logo.png" width="180%" height="100%" style="position: relative; z-index: 50"/>
+ <img src="../../assets/oran-logo.png" width="180%" height="100%" style="position: relative; z-index: 50" />
<svg class="logo__icon" viewBox="150.3 22.2 313.7 42.8">
<text [ngClass]="{'logo__text-dark': darkModeActive}" class="logo__text" fill="#432c85"
font-size="30" font-weight="600"
</div>
- <h3 class="date__text"></h3>
+ <div>
+ <mat-label class="selector-label">Select RIC Instance</mat-label>
+ <mat-form-field>
+ <mat-select (selectionChange)="changeInstance($event.value)" [(value)]="selectedInstanceKey">
+ <mat-option *ngFor="let instance of instanceArray" [value]="instance.key">
+ {{instance.name}}
+ </mat-option>
+ </mat-select>
+ </mat-form-field>
+ </div>
<div class="mode-toggle__container">
<span class="mode-toggle__text">Light</span>
<label class="toggle-button__container">
- <input (click)="modeToggleSwitch()" type="checkbox" class="mode-toggle__input"/>
+ <input (click)="modeToggleSwitch()" type="checkbox" class="mode-toggle__input" />
<span [ngClass]="{'mode-toggle__bg-checked': darkModeActive}" class="mode-toggle__bg"></span>
<span [ngClass]="{'mode-toggle__circle-checked': darkModeActive}" class="mode-toggle__circle"></span>
</label>
width: 100%;
}
}
+
+.selector-label {
+ margin-right: 20px;
+}
\ No newline at end of file
* ========================LICENSE_END===================================
*/
import { Component, OnInit } from '@angular/core';
+import { finalize } from 'rxjs/operators';
+import { RicInstance } from './interfaces/dashboard.types';
+import { InstanceSelectorService } from './services/instance-selector/instance-selector.service';
+import { LoadingDialogService } from './services/ui/loading-dialog.service';
import { UiService } from './services/ui/ui.service';
@Component({
export class RdComponent implements OnInit {
showMenu = false;
darkModeActive: boolean;
+ private instanceArray: RicInstance[];
+ private selectedInstanceKey: string;
- constructor(public ui: UiService) {
+ constructor(
+ public ui: UiService,
+ private instanceSelectorService: InstanceSelectorService,
+ private loadingDialogService: LoadingDialogService) {
}
ngOnInit() {
this.ui.darkModeState.subscribe((value) => {
this.darkModeActive = value;
});
+ this.loadingDialogService.startLoading('Loading RIC instances');
+ this.instanceSelectorService.getInstanceArray()
+ .pipe(
+ finalize(() => this.loadingDialogService.stopLoading())
+ )
+ .subscribe((instanceArray: RicInstance[]) => {
+ this.instanceArray = instanceArray;
+ this.selectedInstanceKey = instanceArray[0].key;
+ })
}
toggleMenu() {
this.ui.darkModeState.next(!this.darkModeActive);
}
+ changeInstance(selectedInstancekey: string) {
+ this.instanceSelectorService.updateSelectedInstance(selectedInstancekey);
+ }
+
}
* limitations under the License.
* ========================LICENSE_END===================================
*/
-import { BrowserModule } from '@angular/platform-browser';
+import { HttpClientModule } from '@angular/common/http';
+import { NgModule } from '@angular/core';
+import { FlexLayoutModule } from '@angular/flex-layout';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatListModule } from '@angular/material/list';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
+import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
-import { BrowserAnimationsModule} from '@angular/platform-browser/animations';
-import { HttpClientModule } from '@angular/common/http';
-import { NgModule } from '@angular/core';
-import { MatRadioModule } from '@angular/material/radio';
import { MatTooltipModule } from '@angular/material/tooltip';
-import { ChartsModule } from 'ng2-charts';
+import { BrowserModule } from '@angular/platform-browser';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MDBBootstrapModule } from 'angular-bootstrap-md';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { MaterialDesignFrameworkModule } from 'angular6-json-schema-form';
+import { ChartsModule } from 'ng2-charts';
import { ToastrModule } from 'ngx-toastr';
+
import { AcXappComponent } from './ac-xapp/ac-xapp.component';
-import { AddDashboardUserDialogComponent } from './user/add-dashboard-user-dialog/add-dashboard-user-dialog.component';
import { AppConfigurationComponent } from './app-configuration/app-configuration.component';
import { AppControlComponent } from './app-control/app-control.component';
-import { AppMgrService } from './services/app-mgr/app-mgr.service';
import { CaasIngressComponent } from './caas-ingress/caas-ingress.component';
-import { CatalogCardComponent } from './ui/catalog-card/catalog-card.component';
import { CatalogComponent } from './catalog/catalog.component';
-import { ConfirmDialogComponent } from './ui/confirm-dialog/confirm-dialog.component';
-import { ControlCardComponent } from './ui/control-card/control-card.component';
import { ControlComponent } from './control/control.component';
-import { DashboardService } from './services/dashboard/dashboard.service';
-import { E2ManagerService } from './services/e2-mgr/e2-mgr.service';
-import { EditDashboardUserDialogComponent } from './user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component';
-import { ErrorDialogComponent } from './ui/error-dialog/error-dialog.component';
-import { ErrorDialogService } from './services/ui/error-dialog.service';
-import { FlexLayoutModule } from '@angular/flex-layout';
+import { CommonService } from './services/common/common.service';
import { FooterComponent } from './footer/footer.component';
-import { LoadingDialogComponent } from './ui/loading-dialog/loading-dialog.component';
import { MainComponent } from './main/main.component';
-import { MaterialDesignFrameworkModule } from 'angular6-json-schema-form';
+import { SidenavListComponent } from './navigation/sidenav-list/sidenav-list.component';
import { PlatformComponent } from './platform/platform.component';
-import { RanControlComponent } from './ran-control/ran-control.component';
import { RanControlConnectDialogComponent } from './ran-control/ran-connection-dialog.component';
-import { RdComponent } from './rd.component';
+import { RanControlComponent } from './ran-control/ran-control.component';
import { RdRoutingModule } from './rd-routing.module';
-import { SidenavListComponent } from './navigation/sidenav-list/sidenav-list.component';
-import { StatCardComponent } from './ui/stat-card/stat-card.component';
-import { StatsComponent } from './stats/stats.component';
+import { RdComponent } from './rd.component';
+import { AppMgrService } from './services/app-mgr/app-mgr.service';
+import { DashboardService } from './services/dashboard/dashboard.service';
+import { E2ManagerService } from './services/e2-mgr/e2-mgr.service';
+import { InstanceSelectorService } from './services/instance-selector/instance-selector.service';
+import { ErrorDialogService } from './services/ui/error-dialog.service';
import { UiService } from './services/ui/ui.service';
+import { StatsComponent } from './stats/stats.component';
+import { CatalogCardComponent } from './ui/catalog-card/catalog-card.component';
+import { ConfirmDialogComponent } from './ui/confirm-dialog/confirm-dialog.component';
+import { ControlCardComponent } from './ui/control-card/control-card.component';
+import { ErrorDialogComponent } from './ui/error-dialog/error-dialog.component';
+import { LoadingDialogComponent } from './ui/loading-dialog/loading-dialog.component';
+import { StatCardComponent } from './ui/stat-card/stat-card.component';
+import { AddDashboardUserDialogComponent } from './user/add-dashboard-user-dialog/add-dashboard-user-dialog.component';
+import { EditDashboardUserDialogComponent } from './user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component';
import { UserComponent } from './user/user.component';
+
+
@NgModule({
declarations: [
AcXappComponent,
],
providers: [
AppMgrService,
+ CommonService,
DashboardService,
E2ManagerService,
ErrorDialogService,
+ InstanceSelectorService,
UiService
],
bootstrap: [RdComponent]
* ========================LICENSE_END===================================
*/
- import { Injectable } from '@angular/core';
- import { HttpClient } from '@angular/common/http';
- import { Observable } from 'rxjs';
- import { map } from 'rxjs/operators';
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
import { ACAdmissionIntervalControl, ACAdmissionIntervalControlAck } from '../../interfaces/ac-xapp.types';
import { DashboardSuccessTransport } from '../../interfaces/dashboard.types';
+import { CommonService } from '../common/common.service';
/**
* Services for calling the Dashboard's A1 endpoints to get/put AC policies.
})
export class ACXappService {
- private basePath = 'api/a1-p';
+ private component = 'a1-p';
private policyPath = 'policies';
private acPolicyName = 'admission_control_policy';
- private buildPath(...args: any[]) {
- let result = this.basePath;
- args.forEach(part => {
- result = result + '/' + part;
- });
- return result;
- }
-
- constructor(private httpClient: HttpClient) {
+ constructor(
+ private httpClient: HttpClient,
+ private commonSvc: CommonService) {
// injects to variable httpClient
}
* @returns Observable that should yield a String
*/
getVersion(): Observable<string> {
- const url = this.buildPath('version');
+ const url = 'api/a1-p/version'
return this.httpClient.get<DashboardSuccessTransport>(url).pipe(
// Extract the string here
map(res => res['data'])
* Gets admission control policy.
* @returns Observable that should yield an ACAdmissionIntervalControl
*/
- getPolicy(): Observable<ACAdmissionIntervalControl> {
- const url = this.buildPath(this.policyPath, this.acPolicyName);
+ getPolicy(instanceKey: string): Observable<ACAdmissionIntervalControl> {
+ const url = this.commonSvc.buildPath(instanceKey, this.component, this.policyPath, this.acPolicyName);
return this.httpClient.get<ACAdmissionIntervalControl>(url);
}
* @param policy an instance of ACAdmissionIntervalControl
* @returns Observable that should yield a response code, no data
*/
- putPolicy(policy: ACAdmissionIntervalControl): Observable<any> {
- const url = this.buildPath(this.policyPath, this.acPolicyName);
+ putPolicy(instanceKey: string, policy: ACAdmissionIntervalControl): Observable<any> {
+ const url = this.commonSvc.buildPath(instanceKey, this.component, this.policyPath, this.acPolicyName);
return this.httpClient.put<ACAdmissionIntervalControlAck>(url, policy, { observe: 'response' });
}
* limitations under the License.
* ========================LICENSE_END===================================
*/
-import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';
+import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
-import { XMXappInfo, XMDeployableApp, XMDeployedApp } from '../../interfaces/app-mgr.types';
+import { XMDeployableApp, XMDeployedApp, XMXappInfo } from '../../interfaces/app-mgr.types';
+import { CommonService } from '../common/common.service';
@Injectable()
export class AppMgrService {
- constructor(private httpClient: HttpClient) {
- // injects to variable httpClient
- }
+ private component = 'appmgr';
- private basePath = 'api/appmgr';
+ constructor(
+ private httpClient: HttpClient,
+ private commonSvc: CommonService) {
+ }
- getDeployable(): Observable<XMDeployableApp[]> {
- return this.httpClient.get<XMDeployableApp[]>(this.basePath + '/xapps/list');
+ getDeployable(instanceKey: string): Observable<XMDeployableApp[]> {
+ const url = this.commonSvc.buildPath(instanceKey, this.component, 'xapps', 'list');
+ return this.httpClient.get<XMDeployableApp[]>(url);
}
- getDeployed(): Observable<XMDeployedApp[]> {
- return this.httpClient.get<XMDeployedApp[]>(this.basePath + '/xapps');
+ getDeployed(instanceKey: string): Observable<XMDeployedApp[]> {
+ const url = this.commonSvc.buildPath(instanceKey, this.component, 'xapps');
+ return this.httpClient.get<XMDeployedApp[]>(url);
}
- deployXapp(name: string): Observable<HttpResponse<Object>> {
+ deployXapp(instanceKey: string, name: string): Observable<HttpResponse<Object>> {
const xappInfo: XMXappInfo = { name: name };
- return this.httpClient.post((this.basePath + '/xapps'), xappInfo, { observe: 'response' });
+ const url = this.commonSvc.buildPath(instanceKey, this.component, 'xapps');
+ return this.httpClient.post(url, xappInfo, { observe: 'response' });
}
- undeployXapp(name: string): Observable<HttpResponse<Object>> {
- return this.httpClient.delete((this.basePath + '/xapps'+ '/' + name), { observe: 'response' });
+ undeployXapp(instanceKey: string, name: string): Observable<HttpResponse<Object>> {
+ const url = this.commonSvc.buildPath(instanceKey, this.component, 'xapps', name);
+ return this.httpClient.delete(url, { observe: 'response' });
}
- getConfig(): Observable<any[]>{
+ getConfig(instanceKey: string): Observable<any[]> {
+ // For demo purpose, pull example config from local
return this.httpClient.get<any[]>("/assets/mockdata/config.json");
- //return this.httpClient.get<any[]>((this.basePath + '/config'));
+ // Once Xapp manager contains layout, should call backend to get xapp config
+ //const url = this.commonSvc.buildPath(instanceKey, this.component, 'config');
+ //return this.httpClient.get<any[]>(url);
}
- putConfig(config: any): Observable<HttpResponse<Object>> {
- return this.httpClient.put((this.basePath + '/config' ), config, { observe: 'response' });
+ putConfig(instanceKey: string, config: any): Observable<HttpResponse<Object>> {
+ const url = this.commonSvc.buildPath(instanceKey, this.component, 'config');
+ return this.httpClient.put(url, config, { observe: 'response' });
}
-
}
* ========================LICENSE_END===================================
*/
-import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
import { V1PodList } from '@kubernetes/client-node';
import { Observable } from 'rxjs';
+import { CommonService } from '../common/common.service';
/**
* Services for calling the Dashboard's caas-ingress endpoints to get Kubernetes details.
})
export class CaasIngressService {
- private basePath = 'api/caas-ingress';
+ private component = 'caas-ingress';
private podsPath = 'pods';
- private buildPath(...args: any[]) {
- let result = this.basePath;
- args.forEach(part => {
- result = result + '/' + part;
- });
- return result;
- }
-
- constructor(private httpClient: HttpClient) {
+ constructor(
+ private httpClient: HttpClient,
+ private commonSvc: CommonService) {
// injects to variable httpClient
}
* Gets list of pods
* @returns Observable that should yield a V1PodList
*/
- getPodList(cluster: string, namespace: string): Observable<V1PodList> {
- const url = this.buildPath('pods', 'cluster', cluster, 'namespace', namespace);
+ getPodList(instanceKey: string, cluster: string, namespace: string): Observable<V1PodList> {
+ const url = this.commonSvc.buildPath(instanceKey, this.component, 'pods', 'cluster', cluster, 'namespace', namespace);
return this.httpClient.get<V1PodList>(url);
}
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { CommonService } from './common.service';
+
+describe('CommonService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: CommonService = TestBed.get(CommonService);
+ expect(service).toBeTruthy();
+ });
+});
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { Injectable } from '@angular/core';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class CommonService {
+
+ constructor() { }
+
+ buildPath(instanceKey: string, component: string, ...args: any[]) {
+ let result = 'api/' + component + '/ric/' + instanceKey;
+ args.forEach(part => {
+ result = result + '/' + part;
+ });
+ return result;
+ }
+
+}
* limitations under the License.
* ========================LICENSE_END===================================
*/
-import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';
+import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
-import { E2RanDetails, E2SetupRequest } from '../../interfaces/e2-mgr.types';
import { DashboardSuccessTransport } from '../../interfaces/dashboard.types';
+import { E2RanDetails, E2SetupRequest } from '../../interfaces/e2-mgr.types';
+import { CommonService } from '../common/common.service';
@Injectable({
providedIn: 'root'
export class E2ManagerService {
- private basePath = 'api/e2mgr/nodeb/';
+ private component = 'e2mgr';
- constructor(private httpClient: HttpClient) {
+ constructor(
+ private httpClient: HttpClient,
+ private commonSvc: CommonService) {
// injects to variable httpClient
}
* Gets E2 client version details
* @returns Observable that should yield a String
*/
- getVersion(): Observable<string> {
- const url = this.basePath + 'version';
+ getVersion(instanceKey: string): Observable<string> {
+ const url = 'api/e2mgr/version';
return this.httpClient.get<DashboardSuccessTransport>(url).pipe(
// Extract the string here
map(res => res['data'])
* Gets RAN details
* @returns Observable that should yield an array of objects
*/
- getRan(): Observable<Array<E2RanDetails>> {
- return this.httpClient.get<Array<E2RanDetails>>(this.basePath + 'ran');
+ getRan(instanceKey: string): Observable<Array<E2RanDetails>> {
+ const url = this.commonSvc.buildPath(instanceKey, this.component, 'nodeb', 'ran');
+ return this.httpClient.get<Array<E2RanDetails>>(url);
}
/**
* Sends a request to setup an ENDC/gNodeB connection
* @returns Observable. On success there is no data, only a code.
*/
- endcSetup(req: E2SetupRequest): Observable<HttpResponse<Object>> {
- return this.httpClient.post(this.basePath + 'endc-setup', req, { observe: 'response' });
+ endcSetup(instanceKey: string, req: E2SetupRequest): Observable<HttpResponse<Object>> {
+ const url = this.commonSvc.buildPath(instanceKey, this.component, 'nodeb', 'endc-setup');
+ return this.httpClient.post(url, req, { observe: 'response' });
}
/**
* Sends a request to setup an X2/eNodeB connection
* @returns Observable. On success there is no data, only a code.
*/
- x2Setup(req: E2SetupRequest): Observable<HttpResponse<Object>> {
- return this.httpClient.post(this.basePath + 'x2-setup', req, { observe: 'response' });
+ x2Setup(instanceKey: string, req: E2SetupRequest): Observable<HttpResponse<Object>> {
+ const url = this.commonSvc.buildPath(instanceKey, this.component, 'nodeb', 'x2-setup');
+ return this.httpClient.post(url, req, { observe: 'response' });
}
/**
* Sends a request to drop all RAN connections
* @returns Observable with body.
*/
- nodebPut(): Observable<any> {
- return this.httpClient.put((this.basePath + 'shutdown'), { observe: 'body' });
+ nodebPut(instanceKey: string): Observable<any> {
+ const url = this.commonSvc.buildPath(instanceKey, this.component, 'nodeb', 'shutdown');
+ return this.httpClient.put(url, { observe: 'body' });
}
}
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { InstanceSelectorService } from './instance-selector.service';
+
+describe('InstanceSelectorService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: InstanceSelectorService = TestBed.get(InstanceSelectorService);
+ expect(service).toBeTruthy();
+ });
+});
--- /dev/null
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { shareReplay, tap } from 'rxjs/operators';
+import { RicInstance } from '../../interfaces/dashboard.types';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class InstanceSelectorService {
+ private selectedInstanceKey: BehaviorSubject<string> = new BehaviorSubject<string>('');
+ private instanceArray: Observable<RicInstance[]>;
+ private basePath = 'api/admin/instance';
+
+ constructor(private httpClient: HttpClient) { }
+
+ getInstanceArray(): Observable<RicInstance[]> {
+ if (this.instanceArray) {
+ return this.instanceArray;
+ }
+ return this.instanceArray = this.httpClient.get<RicInstance[]>(this.basePath)
+ .pipe(
+ tap(ricInstanceArray => {
+ this.initselectedInstanceKey(ricInstanceArray[0].key);
+ }),
+ shareReplay(1)
+ );
+ }
+
+ private initselectedInstanceKey(instanceKey: string) {
+ if (!this.selectedInstanceKey.value) {
+ this.selectedInstanceKey.next(instanceKey)
+ }
+ }
+
+ // This method may return the BehaviorSubject with empty string
+ // Afther subscribe that BehaviorSubject
+ // Please make sure this BehaviorSubject has non empty value
+ getSelectedInstancekey(): BehaviorSubject<string> {
+ return this.selectedInstanceKey;
+ }
+
+ updateSelectedInstance(instanceKey: string) {
+ this.selectedInstanceKey.next(instanceKey)
+ }
+
+}