diff --git a/frontend/src/app/device-overview/device-overview.component.html b/frontend/src/app/device-overview/device-overview.component.html index 2c7cc77..d91595c 100644 --- a/frontend/src/app/device-overview/device-overview.component.html +++ b/frontend/src/app/device-overview/device-overview.component.html @@ -1,7 +1,7 @@
-
Sist oppdatert {{ samples.lastPoll |date:'YYYY-mm-dd HH:MM'}}
+
Sist oppdatert {{ samples.lastDataUpdate |date:'YYYY-mm-dd HH:MM'}}
diff --git a/frontend/src/app/device-view/device-view.component.html b/frontend/src/app/device-view/device-view.component.html index ce08292..4c7ed77 100644 --- a/frontend/src/app/device-view/device-view.component.html +++ b/frontend/src/app/device-view/device-view.component.html @@ -1,12 +1,24 @@
- Sist oppdatert {{ samples.lastPoll |date:'YYYY-mm-dd HH:MM'}} + Sist oppdatert {{ samples.lastDataUpdate |date:'YYYY-mm-dd HH:MM'}}
-
-
{{ metric.bigText }}
-
{{ metric.smallText }}
+
+
{{ metricBLE }}
+
Maks antall bluetooth-enheter
+
+
+
{{ metricWifi }}
+
Maks antall WiFi-enheter
+
+
+
{{ metricSampleCount }}
+
Målinger i perioden
+
+
+
{{ metricDensity }}
+
Folketetthet ({{ metricDensityPercent }}% av maks)
diff --git a/frontend/src/app/device-view/device-view.component.ts b/frontend/src/app/device-view/device-view.component.ts index a319379..2d0fe46 100644 --- a/frontend/src/app/device-view/device-view.component.ts +++ b/frontend/src/app/device-view/device-view.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, ElementRef, Input, OnChanges, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core'; +import { AfterContentInit, AfterViewInit, ChangeDetectorRef, Component, ElementRef, Input, OnChanges, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core'; import { V1Device } from '../api/pax'; import * as d3 from 'd3'; import * as Plot from '@observablehq/plot'; @@ -19,17 +19,21 @@ export class DeviceViewComponent implements OnInit, AfterViewInit, OnChanges { @Input("device") device: V1Device = {}; @ViewChild("chart") chartRef?: ElementRef; - metrics: Metric[] = []; + metricDensity: string = ""; + metricDensityPercent: number = 0; + metricSampleCount: number = 0; + metricWifi: number = 0; + metricBLE: number = 0; + data: DeviceSample[] = []; errorMessage: string = ""; - chartIntervalHours: number = 24; - chart?: (SVGElement | HTMLElement); constructor( protected samples: SampleService, private renderer: Renderer2, + private cd: ChangeDetectorRef, ) { } ngOnInit(): void { @@ -47,9 +51,23 @@ export class DeviceViewComponent implements OnInit, AfterViewInit, OnChanges { } loadData(): void { - this.data = this.samples.dataForDevice(this.device.id!); - this.buildMetrics(); - this.showChart(); + var elements: DeviceSample[] = []; + this.samples.allData().subscribe({ + next: (d: DeviceSample) => { + if (d.id == this.device.id) { + elements.push(d); + } + }, + complete: () => { + this.data = elements; + this.showChart(); + this.buildMetrics(); + // We change the bindings in a change event but it won't cascade. This forces the + // change detection to run one more time without issuing an error + // see: https://angular.io/errors/NG0100 + this.cd.detectChanges(); + }, + }); } showChart(): void { @@ -77,7 +95,7 @@ export class DeviceViewComponent implements OnInit, AfterViewInit, OnChanges { x: { label: "Klokkeslett", grid: true, - tickFormat: d3.utcFormat("%H:%M"), + tickFormat: d3.timeFormat("%H:%M"), domain: [startDate, endDate] }, marks: [ @@ -122,7 +140,7 @@ export class DeviceViewComponent implements OnInit, AfterViewInit, OnChanges { Plot.frame(), ], color: { - legend: true, + legend: false, domain: ["wifi", "ble"], range: ["red", "blue"] }, @@ -132,7 +150,7 @@ export class DeviceViewComponent implements OnInit, AfterViewInit, OnChanges { background: '#eeeeee', fill: '#808080', } - }) + }); this.renderer.appendChild(this.chartRef?.nativeElement, this.chart) } @@ -159,11 +177,10 @@ export class DeviceViewComponent implements OnInit, AfterViewInit, OnChanges { if (percent < 25) { busyIndicator = "Svært Lav"; } - this.metrics = [ - { bigText: String(maxBle), smallText: "Maks antall BLE" }, - { bigText: String(maxWifi), smallText: "Maks antall WiFi" }, - { bigText: String(this.data.length), smallText: "Målinger i perioden" }, - { bigText: busyIndicator, smallText: "Folketetthet (" + percent + "% av maks)" } - ]; + this.metricBLE = maxBle; + this.metricWifi = maxWifi; + this.metricSampleCount = this.data.length; + this.metricDensity = busyIndicator; + this.metricDensityPercent = percent; } } diff --git a/frontend/src/app/main-page/main-page.component.html b/frontend/src/app/main-page/main-page.component.html index 9e9053b..3e60133 100644 --- a/frontend/src/app/main-page/main-page.component.html +++ b/frontend/src/app/main-page/main-page.component.html @@ -7,6 +7,9 @@ PAX + Play + [Playing] + [Stopped]
diff --git a/frontend/src/app/main-page/main-page.component.ts b/frontend/src/app/main-page/main-page.component.ts index 6f85d48..36d6939 100644 --- a/frontend/src/app/main-page/main-page.component.ts +++ b/frontend/src/app/main-page/main-page.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { V1Device } from '../api/pax'; import { SampleService } from '../sample.service'; +import { Subscription, interval } from 'rxjs'; @Component({ selector: 'app-main-page', @@ -11,13 +12,20 @@ export class MainPageComponent implements OnInit { activeDevices: V1Device[] = []; activeDevice?: V1Device; + autoPlayEnabled: boolean = false; + activeStep: number = 0; + autoPlayer?: Subscription; constructor( protected samples: SampleService, ) { + let list: V1Device[] = []; this.samples.activeDevices().subscribe({ next: (d: V1Device) => { - this.activeDevices.push(d); + list.push(d); + }, + complete: () => { + this.activeDevices = list; }, }) } @@ -28,4 +36,33 @@ export class MainPageComponent implements OnInit { setActiveDevice(device?: V1Device): void { this.activeDevice = device; } + + autoPlay(enable: boolean) { + if (!enable) { + this.autoPlayEnabled = false; + this.autoPlayer?.unsubscribe(); + return; + } + this.activeStep = -1; + this.activeDevice = undefined; + this.autoPlayEnabled = true; + this.autoPlayer = interval(15000).subscribe((val) => this.autoStep()); + } + + autoStep(): void { + if (!this.autoPlayEnabled) { + return; + } + this.activeStep++; + if (this.activeStep >= this.activeDevices.length) { + this.activeStep = -1; + } + if (this.activeStep == -1) { + this.activeDevice = undefined; + return; + } + this.activeDevice = this.activeDevices[this.activeStep]; + } + + } diff --git a/frontend/src/app/sample.service.ts b/frontend/src/app/sample.service.ts index 1cea14c..af52b44 100644 --- a/frontend/src/app/sample.service.ts +++ b/frontend/src/app/sample.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { PaxServiceService, V1Data, V1Device, V1ListDataResponse, V1ListDevicesResponse, V1Sample } from './api/pax'; -import { Observable, ReplaySubject, combineLatest, interval } from 'rxjs'; +import { Observable, ReplaySubject, interval } from 'rxjs'; import { HttpErrorResponse } from '@angular/common/http'; // Also a custom sample setup @@ -19,14 +19,12 @@ export class SampleService { public errorMessage: string = ""; - public lastPoll: Date = new Date(); + public lastDataUpdate: Date = new Date(); + private lastPoll: Date = new Date(); // Subject for initial data load private dataSubject = new ReplaySubject(); - // Subjects for individual device load - private deviceSubjects: Map> = new Map>(); - // Subject for updates private updateSubject = new ReplaySubject(); @@ -41,7 +39,7 @@ export class SampleService { return this.activeDeviceSubject; } - //dataUpdater = interval(60000).subscribe((val) => console.debug('Called update', val)); + dataUpdater = interval(60000).subscribe((val) => this.pollForChanges()); constructor( protected paxService: PaxServiceService, @@ -53,6 +51,8 @@ export class SampleService { let chartIntervalHours = 24; let dayAgo: string = "" + (new Date().getTime() - (chartIntervalHours * 3600 * 1000)); let now: string = "" + (new Date().getTime()); + this.lastDataUpdate = new Date(); + this.lastPoll = new Date(); this.paxService.paxServiceListData(dayAgo, now).subscribe({ next: (value: V1ListDataResponse) => { if (value.data) { @@ -88,36 +88,55 @@ export class SampleService { this.dataSubject.complete(); }, complete: () => { - this.lastPoll = new Date(); - - // FIXME: Postpone the complete call when we start polling? this.activeDeviceSubject.complete(); this.dataSubject.complete(); }, }); } - public hasError(): boolean { - return this.errorMessage != ""; + private pollForChanges(): void { + let lastCheck: string = "" + this.lastPoll.getTime(); + this.lastPoll = new Date(); + this.paxService.paxServiceListData(lastCheck).subscribe({ + next: (value: V1ListDataResponse) => { + if (value.data) { + value.data.forEach((data) => this.addDataToSamples(data)); + } + }, + error: (e: HttpErrorResponse) => { + this.errorMessage = e.message; + this.updateSubject.error(e); + }, + complete: () => { + }, + }); } - public dataForDevice(id: string): DeviceSample[] { - let ret = this.allSamples.find((v: V1Data) => v.deviceId == id); - if (ret && ret.samples) { - return ret.samples.map(d => { - return { - id: "", - name: "", - time: new Date(parseInt(d.timestamp!)), - ble: d.bluetoothCount || 0, - wifi: d.wifiCount || 0, - }; - }); + // Add samples to the existing data set + private addDataToSamples(data: V1Data): void { + var exists: boolean = false; + this.allSamples.forEach((set, index) => { + if (set.deviceId == data.deviceId) { + exists = true; + set.samples?.forEach((sample) => { + this.allSamples[index].samples?.push(sample); + }); + console.debug("Added " + (set.samples?.length || 0) + " samples to data for " + set.deviceId) + } + }); + if (!exists) { + console.debug("New device; adding new sample set: ", data); + this.allSamples.push(data); } - return []; + } + + public hasError(): boolean { + return this.errorMessage != ""; } public allData(): Observable { + // TODO: If the data is more than N minutes old return a new sample set return this.dataSubject; } + }