Skip to content

Commit

Permalink
Show overview of gateway status (#84)
Browse files Browse the repository at this point in the history
* Init gateway status overview.

Styling not done. Dummy data

* Adjust gateway status for responsiveness. Added TODOs

* Implement gateway status fetch. Update styling and TODOs

* Reset table page. Update gateway status logic and styling

* Add parent options for paginator and title

* Fix gateway status not fetching on interval change

* Optimize performance on gateway status

* Add comment to latest timestamp method

* Visualize gateway traffic and status (#85)

* Fetch single gateway

* Add empty gateway graphs.

* Visualize gateway packages

* Fix date and merge

Co-authored-by: nlg <[email protected]>

Co-authored-by: nlg <[email protected]>
  • Loading branch information
AramAlsabti and nlg authored May 18, 2022
1 parent a5288c1 commit ed023ce
Show file tree
Hide file tree
Showing 23 changed files with 764 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
<div class="row">
<div class="col-md-6 d-flex align-items-stretch mt-1 mb-4">
<app-graph *ngIf="device.type === 'SIGFOX' || device.type === 'LORAWAN'" [data]="rssiChartData"
[type]="'line'" [options]="rssiChartOptions" [title]="'IOTDEVICE.HISTORY-TAB.RSSI' | translate">
[type]="'line'" [title]="'IOTDEVICE.HISTORY-TAB.RSSI' | translate">
</app-graph>
</div>
<div class="col-md-6 d-flex align-items-stretch mt-1 mb-4">
<app-graph *ngIf="device.type === 'SIGFOX' || device.type === 'LORAWAN'" [data]="snrChartData"
[type]="'line'" [options]="snrChartOptions" [title]="'IOTDEVICE.HISTORY-TAB.SNR' | translate">
[type]="'line'" [title]="'IOTDEVICE.HISTORY-TAB.SNR' | translate">
</app-graph>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,7 @@ import { MatTabChangeEvent } from '@angular/material/tabs';
import { ChartConfiguration } from 'chart.js';
import * as moment from 'moment';
import { recordToEntries } from '@shared/helpers/record.helper';

const colorGraphBlue1 = '#03AEEF';

const defaultChartOptions: ChartConfiguration['options'] = {
plugins: { legend: { display: false }, },
responsive: true,
layout: {
padding: {
top: 15,
left: 10,
right: 10,
}
},
};
import { ColorGraphBlue1 } from '@shared/constants/color-constants';

/**
* Ordered from "worst" to "best" (from DR0 and up)
Expand Down Expand Up @@ -83,17 +70,13 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy {
dataRateChartData: ChartConfiguration['data'] = { datasets: [] };
rssiChartData: ChartConfiguration['data'] = { datasets: [] };
snrChartData: ChartConfiguration['data'] = { datasets: [] };
rssiChartOptions = defaultChartOptions;
snrChartOptions: typeof defaultChartOptions = defaultChartOptions;

dataRateChartOptions: typeof defaultChartOptions = {
...defaultChartOptions,
dataRateChartOptions: ChartConfiguration['options'] = {
scales: {
x: { stacked: true },
y: { stacked: true },
},
plugins: {
...defaultChartOptions,
tooltip: {
mode: 'index',
position: 'average',
Expand Down Expand Up @@ -226,7 +209,7 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy {

this.iotDeviceService.getDeviceStats(this.deviceId).subscribe(
(response) => {
if (response === null) {
if (!response) {
return;
}

Expand All @@ -250,10 +233,10 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy {
},
{
rssiDatasets: [
{ data: [], borderColor: colorGraphBlue1, backgroundColor: colorGraphBlue1 },
{ data: [], borderColor: ColorGraphBlue1, backgroundColor: ColorGraphBlue1 },
],
snrDatasets: [
{ data: [], borderColor: colorGraphBlue1, backgroundColor: colorGraphBlue1 },
{ data: [], borderColor: ColorGraphBlue1, backgroundColor: ColorGraphBlue1 },
],
dataRateDatasets: this.initDataRates(),
labels: [],
Expand Down
5 changes: 5 additions & 0 deletions src/app/gateway/enums/gateway-status-interval.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum GatewayStatusInterval {
DAY = 'DAY',
WEEK = 'WEEK',
MONTH = 'MONTH',
}
74 changes: 46 additions & 28 deletions src/app/gateway/gateway-detail/gateway-detail.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,36 +46,54 @@ <h3>{{ 'GATEWAY.LOCATION' | translate }}</h3>
<div class="row">
<div class="col-12">
<div class="jumbotron">
<h3>{{ 'GATEWAY.STATS' | translate }}</h3>
<div class="mat-elevation-z8">
<div class="loading-shade" *ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<table mat-table [dataSource]="dataSource">
<ng-container matColumnDef="rxPacketsReceived">
<th mat-header-cell *matHeaderCellDef>
{{ 'GATEWAY.STATS-RXPACKETSRECEIVED' | translate }}
</th>
<td mat-cell *matCellDef="let element">{{element.rxPacketsReceived}}</td>
</ng-container>

<ng-container matColumnDef="txPacketsEmitted">
<th mat-header-cell *matHeaderCellDef>{{ 'GATEWAY.STATS-TXPACKETSEMITTED' | translate }}
</th>
<td mat-cell *matCellDef="let element">{{element.txPacketsEmitted}}</td>
</ng-container>
<app-gateway-status [isVisibleSubject]="isGatewayStatusVisibleSubject" [gatewayId]="id" paginatorClass="d-none"
[shouldLinkToDetails]="false" [title]="'GATEWAY.ONLINE-STATUS' | translate">
</app-gateway-status>
</div>

<ng-container matColumnDef="txPacketsReceived">
<th mat-header-cell *matHeaderCellDef>{{ 'GATEWAY.STATS-TIMESTAMP' | translate }}</th>
<td mat-cell *matCellDef="let element">{{element.timestamp | date}}</td>
</ng-container>
<div class="jumbotron">
<h3>{{ 'GATEWAY.DATA-PACKETS' | translate }}</h3>
<div class="loading-shade" *ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator [length]="resultLength" [pageSizeOptions]="[5, 10, 20]" [pageSize]="pageSize" showFirstLastButtons>
</mat-paginator>
<div class="d-flex flex-row mb-4">
<div class="col-md-6 d-flex align-items-stretch mt-1 mb-4">
<app-graph [data]="receivedGraphData" [type]="'line'" [title]="'GATEWAY.STATS-RXPACKETSRECEIVED' | translate"
[graphCardClass]="'shadow-none pl-0'" [graphHeaderClass]="'mat-card-header-text-ml-0'">
</app-graph>
</div>
<div class="col-md-6 d-flex align-items-stretch mt-1 mb-4">
<app-graph [data]="sentGraphData" [type]="'line'" [title]="'GATEWAY.STATS-TXPACKETSEMITTED' | translate"
[graphCardClass]="'shadow-none pl-0'" [graphHeaderClass]="'mat-card-header-text-ml-0'">
</app-graph>
</div>
</div>

<table mat-table [dataSource]="dataSource">
<ng-container matColumnDef="rxPacketsReceived">
<th mat-header-cell *matHeaderCellDef>
{{ 'GATEWAY.STATS-RXPACKETSRECEIVED' | translate }}
</th>
<td mat-cell *matCellDef="let element">{{element.rxPacketsReceived}}</td>
</ng-container>

<ng-container matColumnDef="txPacketsEmitted">
<th mat-header-cell *matHeaderCellDef>{{ 'GATEWAY.STATS-TXPACKETSEMITTED' | translate }}
</th>
<td mat-cell *matCellDef="let element">{{element.txPacketsEmitted}}</td>
</ng-container>

<ng-container matColumnDef="txPacketsReceived">
<th mat-header-cell *matHeaderCellDef>{{ 'GATEWAY.STATS-TIMESTAMP' | translate }}</th>
<td mat-cell *matCellDef="let element">{{element.timestamp | date}}</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator [length]="resultLength" [pageSizeOptions]="[5, 10, 20]" [pageSize]="pageSize" showFirstLastButtons>
</mat-paginator>
</div>
</div>
</div>
</div>
4 changes: 2 additions & 2 deletions src/app/gateway/gateway-detail/gateway-detail.component.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
table {
width: 100%;
}
width: 100%;
}
52 changes: 47 additions & 5 deletions src/app/gateway/gateway-detail/gateway-detail.component.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { AfterViewInit, Component, EventEmitter, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Subscription } from 'rxjs';
import { Subscription, Subject } from 'rxjs';
import { ChirpstackGatewayService } from 'src/app/shared/services/chirpstack-gateway.service';
import { ActivatedRoute, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { BackButton } from '@shared/models/back-button.model';
import { Gateway, GatewayStats } from '../gateway.model';
import { Gateway, GatewayStats, GatewayResponse } from '../gateway.model';
import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service';
import { MeService } from '@shared/services/me.service';
import { environment } from '@environments/environment';
import { DropdownButton } from '@shared/models/dropdown-button.model';
import { ChartConfiguration } from 'chart.js';
import { ColorGraphBlue1 } from '@shared/constants/color-constants';
import { formatDate } from '@angular/common';

@Component({
selector: 'app-gateway-detail',
Expand All @@ -29,11 +32,14 @@ export class GatewayDetailComponent implements OnInit, OnDestroy, AfterViewInit
public gatewaySubscription: Subscription;
public gateway: Gateway;
public backButton: BackButton = { label: '', routerLink: ['gateways'] };
private id: string;
id: string;
deleteGateway = new EventEmitter();
private deleteDialogSubscription: Subscription;
public dropdownButton: DropdownButton;
isLoadingResults = true;
isGatewayStatusVisibleSubject = new Subject<void>();
receivedGraphData: ChartConfiguration['data'] = { datasets: [] };
sentGraphData: ChartConfiguration['data'] = { datasets: [] };

constructor(
private gatewayService: ChirpstackGatewayService,
Expand Down Expand Up @@ -72,7 +78,7 @@ export class GatewayDetailComponent implements OnInit, OnDestroy, AfterViewInit
}

bindGateway(id: string): void {
this.gatewayService.get(id).subscribe((result: any) => {
this.gatewayService.get(id).subscribe((result: GatewayResponse) => {
result.gateway.tagsString = JSON.stringify(result.gateway.tags);
this.gateway = result.gateway;
this.gateway.canEdit = this.canEdit();
Expand All @@ -83,6 +89,9 @@ export class GatewayDetailComponent implements OnInit, OnDestroy, AfterViewInit
this.dataSource.paginator = this.paginator;
this.setDropdownButton();
this.isLoadingResults = false;

this.buildGraphs();
this.isGatewayStatusVisibleSubject.next();
});
}

Expand All @@ -94,11 +103,44 @@ export class GatewayDetailComponent implements OnInit, OnDestroy, AfterViewInit
} : null;
this.translate.get(['LORA-GATEWAY-TABLE-ROW.SHOW-OPTIONS'])
.subscribe(translations => {
this.dropdownButton.label = translations['LORA-GATEWAY-TABLE-ROW.SHOW-OPTIONS']
this.dropdownButton.label = translations['LORA-GATEWAY-TABLE-ROW.SHOW-OPTIONS'];
}
);
}

private buildGraphs() {
const { receivedDatasets, sentDatasets, labels } = this.gatewayStats.reduce(
(
res: {
receivedDatasets: ChartConfiguration['data']['datasets'];
sentDatasets: ChartConfiguration['data']['datasets'];
labels: ChartConfiguration['data']['labels'];
},
data
) => {
res.receivedDatasets[0].data.push(data.rxPacketsReceived);
res.sentDatasets[0].data.push(data.txPacketsEmitted);

// Formatted to stay consistent with the corresponding table. When more languages are added,
// register and use them properly. See https://stackoverflow.com/a/54769064
res.labels.push(formatDate(data.timestamp, 'dd MMM', 'en-US'));
return res;
},
{
receivedDatasets: [
{ data: [], borderColor: ColorGraphBlue1, backgroundColor: ColorGraphBlue1 },
],
sentDatasets: [
{ data: [], borderColor: ColorGraphBlue1, backgroundColor: ColorGraphBlue1 },
],
labels: [],
}
);

this.receivedGraphData = { datasets: receivedDatasets, labels };
this.sentGraphData = { datasets: sentDatasets, labels };
}

canEdit(): boolean {
return this.meService.canWriteInTargetOrganization(this.gateway.internalOrganizationId);
}
Expand Down
10 changes: 8 additions & 2 deletions src/app/gateway/gateway-list/gateway-list.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[dropdownLabel]="'GATEWAY.DROPDOWNFILTER' | translate" (updateSelectedOpt)="setOrgIdFilter($event)"
[dropdownDefaultOption]="'GATEWAY.DROPDOWNDEFAULT' | translate">
</app-top-bar-table>
<mat-tab-group animationDuration="200ms" (selectedTabChange)="showMap($event)">
<mat-tab-group animationDuration="200ms" (selectedTabChange)="selectedTabChange($event)">
<mat-tab label="{{'GATEWAY.TABEL-TAB' | translate}}">
<div class="jumbotron--table">
<app-gateway-table [organisationChangeSubject]="organisationChangeSubject">
Expand All @@ -20,6 +20,12 @@
</app-map>
</div>
</mat-tab>
<mat-tab label="{{'LORA-GATEWAY-TABLE.STATUS' | translate}}">
<div class="jumbotron--table">
<app-gateway-status [organisationChangeSubject]="organisationChangeSubject"
[isVisibleSubject]="isGatewayStatusVisibleSubject"></app-gateway-status>
</div>
</mat-tab>
</mat-tab-group>
</div>
</div>
</div>
39 changes: 24 additions & 15 deletions src/app/gateway/gateway-list/gateway-list.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import { MeService } from '@shared/services/me.service';
import { SharedVariableService } from '@shared/shared-variable/shared-variable.service';
import { environment } from '@environments/environment';
import { Title } from '@angular/platform-browser';
import { MatTabChangeEvent } from '@angular/material/tabs';

const gatewayStatusTabIndex = 2;

@Component({
selector: 'app-gateway-list',
Expand Down Expand Up @@ -41,15 +43,18 @@ export class GatewayListComponent implements OnInit, OnChanges, OnDestroy {
public pageOffset = 0;
public pageTotal: number;
organisationId: number;
organisationChangeSubject: Subject<any> = new Subject();
tabIndex = 0;
organisationChangeSubject: Subject<number> = new Subject();
isGatewayStatusVisibleSubject: Subject<void> = new Subject();

constructor(
public translate: TranslateService,
private chirpstackGatewayService: ChirpstackGatewayService,
private deleteDialogService: DeleteDialogService,
private meService: MeService,
private titleService: Title,
private sharedVariableService: SharedVariableService) {
private sharedVariableService: SharedVariableService,
) {
translate.use('da');
moment.locale('da');
}
Expand All @@ -75,10 +80,15 @@ export class GatewayListComponent implements OnInit, OnChanges, OnDestroy {
}
}

setOrgIdFilter(event: number) {
this.organisationId = event;
this.organisationChangeSubject.next(event);
this.filterGatewayByOrgId(event);
setOrgIdFilter(orgId: number) {
this.organisationId = orgId;
this.organisationChangeSubject.next(orgId);

if (this.tabIndex === gatewayStatusTabIndex) {
this.isGatewayStatusVisibleSubject.next();
}

this.filterGatewayByOrgId(orgId);
}

private getGateways(): void {
Expand Down Expand Up @@ -120,14 +130,18 @@ export class GatewayListComponent implements OnInit, OnChanges, OnDestroy {
);
}

showMap(event: any) {
if (event.index === 1) {
selectedTabChange({index}: MatTabChangeEvent) {
this.tabIndex = index;

if (index === 1) {
if (this.selectedOrg) {
this.getGatewayWith(this.selectedOrg);
} else {
this.getGateways();
}
this.showmap = true;
} else if (index === gatewayStatusTabIndex) {
this.isGatewayStatusVisibleSubject.next();
}
}

Expand Down Expand Up @@ -184,12 +198,7 @@ export class GatewayListComponent implements OnInit, OnChanges, OnDestroy {

ngOnDestroy() {
// prevent memory leak by unsubscribing
if (this.gatewaySubscription) {
this.gatewaySubscription.unsubscribe();
}
if (this.deleteDialogSubscription) {
this.deleteDialogSubscription.unsubscribe();
}
this.gatewaySubscription?.unsubscribe();
this.deleteDialogSubscription?.unsubscribe();
}

}
Loading

0 comments on commit ed023ce

Please sign in to comment.