Skip to content

Commit

Permalink
Fixed routing of gateway list + fixed memory leak (#144)
Browse files Browse the repository at this point in the history
* Fixed routing of gateway list + fixed memory leak by unsubscribing properly from gateway fetches

* Fixed routing errors in gateway list
  • Loading branch information
fcv-iteratorIt committed Dec 1, 2023
1 parent 9c2fb6a commit e96a976
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 148 deletions.
10 changes: 9 additions & 1 deletion src/app/gateway/gateway-overview/gateway-overview.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { environment } from '@environments/environment';
import { Title } from '@angular/platform-browser';
import { OrganizationAccessScope } from '@shared/enums/access-scopes';
import { GatewayService } from '@app/gateway/gateway.service';
import { Router } from '@angular/router';
import { NavigationEnd, Router } from '@angular/router';

@Component({
selector: 'app-gateway-list',
Expand Down Expand Up @@ -40,6 +40,7 @@ export class GatewayOverviewComponent implements OnInit, OnChanges, OnDestroy {
organisations: Organisation[];

private deleteDialogSubscription: Subscription;
private routerSubscription: Subscription;
canEdit: boolean;

constructor(
Expand Down Expand Up @@ -68,6 +69,12 @@ export class GatewayOverviewComponent implements OnInit, OnChanges, OnDestroy {
if (this.router.url === '/gateways') {
this.router.navigateByUrl('/gateways/list', { replaceUrl: true });
}
// Subscribe to route change to root and route to list view
this.routerSubscription = this.router.events.subscribe((e) => {
if (e instanceof NavigationEnd && e.url === '/gateways') {
this.router.navigateByUrl('/gateways/list', { replaceUrl: true });
}
});
}

ngOnChanges() {}
Expand All @@ -82,5 +89,6 @@ export class GatewayOverviewComponent implements OnInit, OnChanges, OnDestroy {
// prevent memory leak by unsubscribing
this.gatewaySubscription?.unsubscribe();
this.deleteDialogSubscription?.unsubscribe();
this.routerSubscription?.unsubscribe();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AfterViewInit, Component, OnInit } from '@angular/core';
import { AfterViewInit, Component } from '@angular/core';
import { GatewayService } from '@app/gateway/gateway.service';

@Component({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class GatewayMapComponent implements OnInit, OnDestroy, AfterViewInit {
public gateways: Gateway[];
public coordinateList = [];
private gatewaySubscription: Subscription;
private organizationChangeSubscription: Subscription;
isLoadingResults = true;

constructor(
Expand All @@ -26,13 +27,15 @@ export class GatewayMapComponent implements OnInit, OnDestroy, AfterViewInit {
ngOnInit(): void {}

ngAfterViewInit() {
this.gatewayService.organisationChangeSubject.subscribe((x) => {
if (x) {
this.getGatewayWith(x);
} else {
this.getGateways();
this.organizationChangeSubscription = this.gatewayService.organisationChangeSubject.subscribe(
(x) => {
if (x) {
this.getGatewayWith(x);
} else {
this.getGateways();
}
}
});
);
if (this.gatewayService.selectedOrg) {
this.getGatewayWith(this.gatewayService.selectedOrg);
} else {
Expand Down Expand Up @@ -110,6 +113,6 @@ export class GatewayMapComponent implements OnInit, OnDestroy, AfterViewInit {
ngOnDestroy() {
// prevent memory leak by unsubscribing
this.gatewaySubscription?.unsubscribe();
// this.deleteDialogSubscription?.unsubscribe();
this.organizationChangeSubscription.unsubscribe();
}
}
287 changes: 148 additions & 139 deletions src/app/gateway/gateway-table/gateway-table.component.ts
Original file line number Diff line number Diff line change
@@ -1,139 +1,148 @@
import { ChirpstackGatewayService } from 'src/app/shared/services/chirpstack-gateway.service';
import { TranslateService } from '@ngx-translate/core';
import { Gateway, GatewayResponseMany } from '../gateway.model';
import {
faExclamationTriangle,
faCheckCircle,
} from '@fortawesome/free-solid-svg-icons';
import moment from 'moment';
import { Component, ViewChild, AfterViewInit, Input } from '@angular/core';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { Observable, of as observableOf, Subject } from 'rxjs';
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
import { MeService } from '@shared/services/me.service';
import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service';
import { environment } from '@environments/environment';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { tableSorter } from '@shared/helpers/table-sorting.helper';
import { OrganizationAccessScope } from '@shared/enums/access-scopes';
import { DefaultPageSizeOptions } from '@shared/constants/page.constants';

@Component({
selector: 'app-gateway-table',
templateUrl: './gateway-table.component.html',
styleUrls: ['./gateway-table.component.scss'],
})
export class GatewayTableComponent implements AfterViewInit {
@Input() organisationChangeSubject: Subject<any>;
organizationId?: number;
displayedColumns: string[] = [
'name',
'gateway-id',
'location',
'internalOrganizationName',
'last-seen',
'status',
'menu',
];
data: Gateway[] = [];
dataSource: MatTableDataSource<Gateway>;
public pageSize = environment.tablePageSize;
public pageSizeOptions = DefaultPageSizeOptions;

faExclamationTriangle = faExclamationTriangle;
faCheckCircle = faCheckCircle;
refetchIntervalId: NodeJS.Timeout;
batteryStatusColor = 'green';
batteryStatusPercentage = 50;
resultsLength = 0;
isLoadingResults = true;

@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;

constructor(
private chirpstackGatewayService: ChirpstackGatewayService,
public translate: TranslateService,
private meService: MeService,
private deleteDialogService: DeleteDialogService
) {
this.translate.use('da');
moment.locale('da');
}

ngAfterViewInit() {
this.organisationChangeSubject.subscribe((x) => {
this.organizationId = x;
this.refresh();
});
this.refetchIntervalId = setInterval(() => this.refresh(), 60 * 1000)
this.refresh();
}

ngOnDestroy() {
clearInterval(this.refetchIntervalId)
}

private refresh() {
this.getGateways().subscribe((data) => {
data.result.forEach((gw) => {
gw.canEdit = this.canEdit(gw.internalOrganizationId);
});
this.data = data.result;
this.resultsLength = data.totalCount;
this.isLoadingResults = false;
this.dataSource = new MatTableDataSource(this.data);
this.dataSource.sortingDataAccessor = tableSorter;
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
});
}

canEdit(internalOrganizationId: number): boolean {
return this.meService.hasAccessToTargetOrganization(OrganizationAccessScope.GatewayWrite, internalOrganizationId);
}

private getGateways(): Observable<GatewayResponseMany> {
const params = {
limit: this.paginator.pageSize,
offset: this.paginator.pageIndex * this.paginator.pageSize,
};
if (this.organizationId > 0) {
params['organizationId'] = this.organizationId;
}
return this.chirpstackGatewayService.getMultiple(params);
}

gatewayStatus(gateway: Gateway): boolean {
return this.chirpstackGatewayService.isGatewayActive(gateway);
}

lastActive(gateway: Gateway): string {
if (gateway?.lastSeenAt) {
const lastSeenAtUnixTimestamp = moment(gateway?.lastSeenAt).valueOf();
const now = moment(new Date()).valueOf();
return moment(Math.min(lastSeenAtUnixTimestamp, now)).fromNow();
} else {
return this.translate.instant('ACTIVITY.NEVER');
}
}

clickDelete(element) {
this.deleteGateway(element.id);
}

deleteGateway(id: string) {
this.deleteDialogService.showSimpleDialog().subscribe((response) => {
if (response) {
this.chirpstackGatewayService.delete(id).subscribe((response) => {
if (response.ok && response.body.success === true) {
this.refresh();
}
});
} else {
console.error(response);
}
});
}
}
import { ChirpstackGatewayService } from 'src/app/shared/services/chirpstack-gateway.service';
import { TranslateService } from '@ngx-translate/core';
import { Gateway, GatewayResponseMany } from '../gateway.model';
import {
faExclamationTriangle,
faCheckCircle,
} from '@fortawesome/free-solid-svg-icons';
import moment from 'moment';
import {
Component,
ViewChild,
AfterViewInit,
Input,
OnDestroy,
} from '@angular/core';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { Observable, of as observableOf, Subject, Subscription } from 'rxjs';
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
import { MeService } from '@shared/services/me.service';
import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service';
import { environment } from '@environments/environment';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { tableSorter } from '@shared/helpers/table-sorting.helper';
import { OrganizationAccessScope } from '@shared/enums/access-scopes';
import { DefaultPageSizeOptions } from '@shared/constants/page.constants';

@Component({
selector: 'app-gateway-table',
templateUrl: './gateway-table.component.html',
styleUrls: ['./gateway-table.component.scss'],
})
export class GatewayTableComponent implements AfterViewInit, OnDestroy {
@Input() organisationChangeSubject: Subject<any>;
organizationId?: number;
displayedColumns: string[] = [
'name',
'gateway-id',
'location',
'internalOrganizationName',
'last-seen',
'status',
'menu',
];
data: Gateway[] = [];
dataSource: MatTableDataSource<Gateway>;
public pageSize = environment.tablePageSize;
public pageSizeOptions = DefaultPageSizeOptions;

faExclamationTriangle = faExclamationTriangle;
faCheckCircle = faCheckCircle;
refetchIntervalId: NodeJS.Timeout;
resultsLength = 0;
isLoadingResults = true;
private fetchSubscription: Subscription;

@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;

constructor(
private chirpstackGatewayService: ChirpstackGatewayService,
public translate: TranslateService,
private meService: MeService,
private deleteDialogService: DeleteDialogService
) {
this.translate.use('da');
moment.locale('da');
}

ngAfterViewInit() {
this.fetchSubscription = this.organisationChangeSubject.subscribe((x) => {
this.organizationId = x;
this.refresh();
});
this.refetchIntervalId = setInterval(() => this.refresh(), 60 * 1000);
this.refresh();
}

ngOnDestroy() {
clearInterval(this.refetchIntervalId);
this.fetchSubscription.unsubscribe();
}

private refresh() {
this.getGateways().subscribe((data) => {
data.result.forEach((gw) => {
gw.canEdit = this.canEdit(gw.internalOrganizationId);
});
this.data = data.result;
this.resultsLength = data.totalCount;
this.isLoadingResults = false;
this.dataSource = new MatTableDataSource(this.data);
this.dataSource.sortingDataAccessor = tableSorter;
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
});
}

canEdit(internalOrganizationId: number): boolean {
return this.meService.hasAccessToTargetOrganization(
OrganizationAccessScope.GatewayWrite,
internalOrganizationId
);
}

private getGateways(): Observable<GatewayResponseMany> {
const params = {
limit: this.paginator.pageSize,
offset: this.paginator.pageIndex * this.paginator.pageSize,
};
if (this.organizationId > 0) {
params['organizationId'] = this.organizationId;
}
return this.chirpstackGatewayService.getMultiple(params);
}

gatewayStatus(gateway: Gateway): boolean {
return this.chirpstackGatewayService.isGatewayActive(gateway);
}

lastActive(gateway: Gateway): string {
if (gateway?.lastSeenAt) {
const lastSeenAtUnixTimestamp = moment(gateway?.lastSeenAt).valueOf();
const now = moment(new Date()).valueOf();
return moment(Math.min(lastSeenAtUnixTimestamp, now)).fromNow();
} else {
return this.translate.instant('ACTIVITY.NEVER');
}
}

clickDelete(element) {
this.deleteGateway(element.id);
}

deleteGateway(id: string) {
this.deleteDialogService.showSimpleDialog().subscribe((response) => {
if (response) {
this.chirpstackGatewayService.delete(id).subscribe((response) => {
if (response.ok && response.body.success === true) {
this.refresh();
}
});
} else {
console.error(response);
}
});
}
}

0 comments on commit e96a976

Please sign in to comment.