Skip to content
Merged
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