Devices can now subscribe to specific topics.

- Device can now become (in)active
- Error alert makes sound
- Alerts now execute function on click
This commit is contained in:
2020-11-14 21:15:05 +01:00
parent ab1b90d020
commit 8b54431449
20 changed files with 171 additions and 42 deletions

View File

@@ -6,6 +6,7 @@ export class Alert {
type: AlertType;
message: string;
title?: string;
onClick?: () => void;
duration = 5;
isClosing = false;
@@ -26,6 +27,8 @@ export class AlertService {
private subject = new Subject<Alert>();
private defaultId = 'default-alert';
private errorSoundPath = 'assets/sounds/error.mp3';
// enable subscribing to alerts observable
onAlert(id = this.defaultId): Observable<Alert> {
return this.subject.asObservable();
@@ -37,6 +40,10 @@ export class AlertService {
}
error(message: string, title = 'Error!', options?: any): void {
const audio = new Audio(this.errorSoundPath);
audio.load();
audio.play();
this.alert(new Alert({ ...options, type: AlertType.Error, message, title }));
}

View File

@@ -1,4 +1,4 @@
<div [ngClass]="cssClass(alert)" *ngFor="let alert of this.alerts">
<div [ngClass]="cssClass(alert)" *ngFor="let alert of this.alerts" (click)="alert.onClick()">
<span class="title">{{alert.title}}</span>
<span class="message">{{alert.message}}</span>
<fa-icon [icon]="faClose" (click)="removeAlert(alert)"></fa-icon>

View File

@@ -20,6 +20,7 @@
vertical-align: middle;
transition: 0.25s ease;
animation: appear 0.5s ease;
cursor: pointer;
box-shadow:
0 4.5px 5.3px rgba(0, 0, 0, 0.121),
@@ -56,6 +57,7 @@
.alert-warning {
background-color: #F3CC17 !important;
color: #000 !important;
}
.fadeOut {

View File

@@ -30,9 +30,11 @@ export class AlertComponent implements OnInit, OnDestroy {
this.alerts.push(alert);
this.alerts = this.alerts.reverse();
setTimeout(() => {
this.removeAlert(alert);
}, alert.duration * 1000);
if (alert.duration !== 0) {
setTimeout(() => {
this.removeAlert(alert);
}, alert.duration * 1000);
}
});
}
@@ -68,6 +70,15 @@ export class AlertComponent implements OnInit, OnDestroy {
classes.push(alertTypeClass[alert.type]);
// Save the original onClick function.
const actualFunction = alert.onClick || null;
// Override onClick function so that notification disappears after clicked.
alert.onClick = () => {
if (actualFunction !== null) { actualFunction(); }
this.removeAlert(alert);
};
if (alert.isClosing) { classes.push('fadeOut'); }
return classes.join(' ');

View File

@@ -1,5 +1,4 @@
<h2>Create new user</h2>
<button (click)="alert()">Make info</button>
<form (ngSubmit)="createUser()" #form="ngForm">
<input name="username" [(ngModel)]="newUsername" placeholder="Username"><br>
<input name="password" [(ngModel)]="newPassword" type="password" placeholder="Password"><br>

View File

@@ -1,4 +1,5 @@
import { AfterContentInit, Component, OnDestroy, OnInit } from '@angular/core';
import { resolve } from 'dns';
import { APIService, UserType } from '../api.service';
import { AlertService } from '../_alert/alert.service';
@@ -15,7 +16,7 @@ export class AdminComponent implements AfterContentInit, OnDestroy {
newType: UserType;
invitationCode: string;
constructor(public api: APIService, private alertt: AlertService) { }
constructor(public api: APIService, private alert: AlertService) { }
ngAfterContentInit(): void {
this.api.showFilter = false;
@@ -27,11 +28,17 @@ export class AdminComponent implements AfterContentInit, OnDestroy {
}
async createUser(): Promise<void> {
this.invitationCode = await this.api.createUser(this.newUsername, this.newPassword, this.newType);
}
alert() {
this.alertt.info('This is a test from admin', 'Admin says');
this.api.createUser(this.newUsername, this.newPassword, this.newType)
.catch(error => {
if (error.statusCode === 409) {
this.alert.error('This user already exists');
} else {
this.alert.error('There was an error while creating this user');
}
}).then((val: string) => {
this.invitationCode = val;
this.alert.success(`User ${this.newUsername} created.`, 'Created');
});
}
}

View File

@@ -4,6 +4,7 @@ import { MqttService } from 'ngx-mqtt';
import { BehaviorSubject } from 'rxjs';
import * as moment from 'moment';
import { error } from 'protractor';
import { AlertService } from './_alert/alert.service';
export interface ILogin {
token: string;
@@ -107,7 +108,7 @@ export class APIService {
API_ENDPOINT = 'http://192.168.178.26:8040';
constructor(private httpClient: HttpClient, private mqtt: MqttService) { }
constructor(private httpClient: HttpClient, private mqtt: MqttService, private alert: AlertService) { }
private mqttInit(): void {
// Connect with RabbitMQ after we received our user information
@@ -120,12 +121,31 @@ export class APIService {
password: this.user.brokerToken
});
this.mqtt.observe('/').subscribe(message => {
if (this.beats !== undefined) {
const obj = JSON.parse(message.payload.toString()) as IBeat;
this.beats.push(obj);
this.beatsEvent.next([obj]); // We just push one, so all the map doesn't has to rebuild the entire map.
this.beatStats.totalBeats++;
this.mqtt.observe(this.user._id).subscribe(message => {
if (message !== undefined || message !== null) {
const obj = JSON.parse(message.payload.toString());
switch (obj.type) {
case 'beat':
if (this.beats !== undefined) {
this.beats.push(obj);
this.beatsEvent.next([obj]); // We just push one, so the map doesn't has to rebuild everything from scratch.
this.beatStats.totalBeats++;
}
break;
case 'phone_available':
this.alert.info(`Device ${obj.name} is now online`, 'Device');
break;
case 'phone_register':
this.alert.success(`New device "${obj.displayName}"`, 'New device');
break;
case 'phone_alive':
this.alert.info('Device is now active', obj.displayName.toString());
break;
case 'phone_dead':
this.alert.warn('Device is now offline', obj.displayName.toString());
break;
}
}
});
}
@@ -149,6 +169,7 @@ export class APIService {
await this.getBeats();
await this.getBeatStats();
this.loginEvent.next(true);
this.alert.success('Login successful', 'Login', { duration: 2 });
resolve(token as ILogin);
});
});
@@ -175,8 +196,11 @@ export class APIService {
.subscribe((res: any) => {
this.fetchingDataEvent.next(false);
console.log('Response:', res);
resolve(res.setupToken);
},
error => {
reject(error);
});
});
}
@@ -331,6 +355,16 @@ export class APIService {
return earthRadiusKm * c;
}
/**
* Looks if certain unique objects are present in provided object `obj`.
*
* If yes, it's a beat (true)
* @param obj Object where you think it could be a beat.
*/
isBeatObject(obj: any): boolean {
return (obj._id instanceof String && obj.battery instanceof Number && obj.accuracy instanceof Number);
}
/**
* Short form for `this.api.beats[this.api.beats.length - 1]`
*

View File

@@ -2,10 +2,18 @@
<div id="header" *ngIf="this.api.loginEvent.value">
<ul class="navbar">
<li><a [routerLink]="['/dashboard']" routerLinkActive="router-link-active" >Dashboard</a></li>
<li><a [routerLink]="['/map']" routerLinkActive="router-link-active" >Map</a></li>
<li class="navbar-right"><a [routerLink]="['/user', this.api.user._id]" routerLinkActive="router-link-active" >{{this.api.username}}</a></li>
<li class="navbar-right"><a [routerLink]="['/admin']" routerLinkActive="router-link-active" *ngIf="this.api.user.type == 'admin'">Admin settings</a></li>
<li><a [routerLink]="['/dashboard']" routerLinkActive="router-link-active">Dashboard</a></li>
<li><a [routerLink]="['/map']" routerLinkActive="router-link-active">Map</a></li>
<li class="navbar-right"><a [routerLink]="['/user', this.api.user._id]"
routerLinkActive="router-link-active">
<fa-icon [icon]="faUser"></fa-icon>
{{this.api.username}}</a></li>
<li class="navbar-right"><a [routerLink]="['/admin']" routerLinkActive="router-link-active"
*ngIf="this.api.user.type == 'admin'">
Admin settings</a>
</li>
</ul>
</div>
<div class="header-spacer"></div>

View File

@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { faUser } from '@fortawesome/free-solid-svg-icons';
import { APIService } from './api.service';
import { AlertService } from './_alert/alert.service';
@@ -11,6 +12,7 @@ export class AppComponent implements OnInit{
title = 'Livebeat';
showOverlay = false;
faUser = faUser;
constructor(public api: APIService, private alert: AlertService) {
this.api.fetchingDataEvent.subscribe(status => {
@@ -20,8 +22,7 @@ export class AppComponent implements OnInit{
async ngOnInit(): Promise<void> {
await this.api.login('admin', '$1KDaNCDlyXAOg');
this.alert.success('This is just a test', 'Test');
this.alert.error('Requested user doesn\'t exist', 'Not found');
this.alert.error('Audio test');
return;
}
}

View File

@@ -58,7 +58,7 @@ export class DashboardComponent implements AfterViewInit {
this.api.beatsEvent.subscribe(beats => {
// Only reset array if this is not an update.
if (beats.length !== 1) {
if (beats.length > 1) {
this.batteryLineChartData[0].data = [];
this.batteryLineChartLabels = [];
}

View File

@@ -159,8 +159,6 @@ export class MapComponent {
}
buildMap(isUpdate: boolean, maxAccuracy: number = 30): void {
console.log(isUpdate);
// If this is an update don't rebuild entire map.
if (!isUpdate) {
if (this.api.beats.length === 0) {

View File

@@ -7,7 +7,7 @@
<h2 *ngIf="showDevices">Devices</h2>
<ul class="phoneListing" *ngIf="showDevices">
<li *ngFor="let phone of this.api.phones">
<h2>{{phone.displayName}} <span class="lastBeat">last beat was {{ this.lastBeats.get(phone._id) }}</span></h2>
<h2 [ngClass]="{offline: !phone.active}">{{phone.displayName}} <span class="lastBeat">last beat was {{ this.lastBeats.get(phone._id) }}</span></h2>
<p>{{phone.modelName}}</p>
</li>
</ul>

View File

@@ -25,6 +25,10 @@
background-color: $darker;
}
.offline {
color: #ff6464
}
.lastBeat {
font-weight: lighter;
font-size: 10pt;

View File

@@ -8,7 +8,7 @@ import { ActivatedRoute } from '@angular/router';
templateUrl: './user.component.html',
styleUrls: ['./user.component.scss']
})
export class UserComponent implements AfterContentInit, OnDestroy {
export class UserComponent implements AfterContentInit, OnDestroy, OnInit {
lastLogin: string;
lastBeats: Map<string, string> = new Map<string, string>();
@@ -60,7 +60,12 @@ export class UserComponent implements AfterContentInit, OnDestroy {
ngAfterContentInit(): void {
this.api.showFilter = false;
console.log(this.api.showFilter);
}
ngOnInit(): void {
if (this.api.hasSession()) {
this.api.getPhones();
}
}
ngOnDestroy(): void {

Binary file not shown.