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:
@@ -58,7 +58,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val response = client.newCall(req).execute()
|
val response = client.newCall(req).execute()
|
||||||
|
|
||||||
if (response.code != 200) {
|
if (response.code != 200) {
|
||||||
Snackbar.make(findViewById<FloatingActionButton>(R.id.fab), "Device isn't registered yet.", Snackbar.LENGTH_SHORT)
|
Snackbar.make(findViewById<FloatingActionButton>(R.id.fab), "Device isn't registered yet. Registering ...", Snackbar.LENGTH_SHORT)
|
||||||
.setBackgroundTint(Color.YELLOW)
|
.setBackgroundTint(Color.YELLOW)
|
||||||
.setTextColor(Color.BLACK)
|
.setTextColor(Color.BLACK)
|
||||||
.show()
|
.show()
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ async function run() {
|
|||||||
/**
|
/**
|
||||||
* Database connection
|
* Database connection
|
||||||
*/
|
*/
|
||||||
mongoose.set('debug', true);
|
//mongoose.set('debug', true);
|
||||||
const connection = await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true }).catch((err) => {
|
const connection = await mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true }).catch((err) => {
|
||||||
logger.crit("Database connection could not be made: ", err);
|
logger.crit("Database connection could not be made: ", err);
|
||||||
exit(1);
|
exit(1);
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Response } from "express";
|
import { Response } from "express";
|
||||||
import { logger } from "../app";
|
import { logger, rabbitmq } from "../app";
|
||||||
import { LivebeatRequest } from "../lib/request";
|
import { LivebeatRequest } from "../lib/request";
|
||||||
import { Beat } from "../models/beat/beat.model.";
|
import { Beat } from "../models/beat/beat.model.";
|
||||||
import { Phone } from "../models/phone/phone.model";
|
import { Phone } from "../models/phone/phone.model";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function GetPhone(req: LivebeatRequest, res: Response) {
|
export async function GetPhone(req: LivebeatRequest, res: Response) {
|
||||||
const phoneId: String = req.params['id'];
|
const phoneId: String = req.params['id'];
|
||||||
|
|
||||||
@@ -16,7 +18,7 @@ export async function GetPhone(req: LivebeatRequest, res: Response) {
|
|||||||
|
|
||||||
// Check database for phone
|
// Check database for phone
|
||||||
const phone = await Phone.findOne({ androidId: phoneId, user: req.user?._id });
|
const phone = await Phone.findOne({ androidId: phoneId, user: req.user?._id });
|
||||||
if (phone === undefined) {
|
if (phone === null) {
|
||||||
res.status(404).send();
|
res.status(404).send();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -53,7 +55,7 @@ export async function PostPhone(req: LivebeatRequest, res: Response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create phone
|
// Create phone
|
||||||
await Phone.create({
|
const newPhone = await Phone.create({
|
||||||
androidId,
|
androidId,
|
||||||
displayName,
|
displayName,
|
||||||
modelName,
|
modelName,
|
||||||
@@ -63,7 +65,8 @@ export async function PostPhone(req: LivebeatRequest, res: Response) {
|
|||||||
active: false
|
active: false
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(`New device (${displayName}) registered for ${req.user?.name}.`)
|
logger.info(`New device (${displayName}) registered for ${req.user?.name}.`);
|
||||||
|
rabbitmq.publish(req.user?.id, newPhone.toJSON(), 'phone_register')
|
||||||
|
|
||||||
res.status(200).send();
|
res.status(200).send();
|
||||||
}
|
}
|
||||||
@@ -242,6 +242,34 @@ export async function Resource(req: Request, res: Response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function Topic(req: Request, res: Response) {
|
export async function Topic(req: Request, res: Response) {
|
||||||
|
res.status(200);
|
||||||
|
|
||||||
|
const username = req.query.username;
|
||||||
|
const routingKey = req.query.routing_key;
|
||||||
|
|
||||||
|
if (routingKey === undefined || username === undefined) {
|
||||||
|
res.send('deny');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's us
|
||||||
|
if (username.toString() == 'backend') {
|
||||||
|
res.status(200).send('allow');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
const user = await User.findOne({ name: username.toString() });
|
||||||
|
if (user === null) {
|
||||||
|
res.send('deny');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routingKey !== user.id) {
|
||||||
|
res.send('deny');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
res.status(200).send('allow');
|
res.status(200).send('allow');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export class RabbitMQ {
|
|||||||
connection: amqp.Connection | null = null;
|
connection: amqp.Connection | null = null;
|
||||||
channel: amqp.Channel | null = null;
|
channel: amqp.Channel | null = null;
|
||||||
|
|
||||||
|
timeouts: Map<string, Timeout> = new Map<string, Timeout>();
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
this.connection = await amqp.connect(RABBITMQ_URI);
|
this.connection = await amqp.connect(RABBITMQ_URI);
|
||||||
this.channel = await this.connection.createChannel();
|
this.channel = await this.connection.createChannel();
|
||||||
@@ -26,7 +28,7 @@ export class RabbitMQ {
|
|||||||
// Get phone
|
// Get phone
|
||||||
const phone = await Phone.findOne({ androidId: msg.token });
|
const phone = await Phone.findOne({ androidId: msg.token });
|
||||||
if (phone == undefined) {
|
if (phone == undefined) {
|
||||||
logger.info(`Received beat from unknown device with id ${msg.token}`);
|
logger.warning(`Received beat from unknown device with id ${msg.token}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,12 +44,32 @@ export class RabbitMQ {
|
|||||||
createdAt: msg.timestamp
|
createdAt: msg.timestamp
|
||||||
});
|
});
|
||||||
|
|
||||||
this.channel!.publish('amq.topic', '.', Buffer.from(JSON.stringify(newBeat.toJSON())));
|
// Broadcast if device became active
|
||||||
|
if (this.timeouts.has(phone.id)) {
|
||||||
|
clearTimeout(this.timeouts.get(phone.id));
|
||||||
|
} else {
|
||||||
|
logger.debug('Set phone active');
|
||||||
|
phone.active = true;
|
||||||
|
await phone.save();
|
||||||
|
this.publish(phone.user.toString(), phone.toJSON(), 'phone_alive');
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeoutTimer = setTimeout(async () => {
|
||||||
|
this.publish(phone.user.toString(), phone.toJSON(), 'phone_dead');
|
||||||
|
this.timeouts.delete(phone.id);
|
||||||
|
phone.active = false;
|
||||||
|
await phone.save();
|
||||||
|
}, 30_000);
|
||||||
|
this.timeouts.set(phone.id, timeoutTimer);
|
||||||
|
|
||||||
|
this.publish(phone.user.toString(), newBeat.toJSON(), 'beat');
|
||||||
}, { noAck: true });
|
}, { noAck: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async publish(queueName = 'tracker', data: any) {
|
async publish(userId: string, data: any, type: 'beat' | 'phone_alive' | 'phone_dead' | 'phone_register' | 'panic' = 'beat') {
|
||||||
if (this.connection == undefined) await this.init()
|
if (this.connection == undefined) await this.init()
|
||||||
this.channel?.sendToQueue(queueName, Buffer.from(data));
|
|
||||||
|
data = { type, ...data };
|
||||||
|
this.channel?.publish('amq.topic', userId, Buffer.from(JSON.stringify(data)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ export class Alert {
|
|||||||
type: AlertType;
|
type: AlertType;
|
||||||
message: string;
|
message: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
onClick?: () => void;
|
||||||
duration = 5;
|
duration = 5;
|
||||||
isClosing = false;
|
isClosing = false;
|
||||||
|
|
||||||
@@ -26,6 +27,8 @@ export class AlertService {
|
|||||||
private subject = new Subject<Alert>();
|
private subject = new Subject<Alert>();
|
||||||
private defaultId = 'default-alert';
|
private defaultId = 'default-alert';
|
||||||
|
|
||||||
|
private errorSoundPath = 'assets/sounds/error.mp3';
|
||||||
|
|
||||||
// enable subscribing to alerts observable
|
// enable subscribing to alerts observable
|
||||||
onAlert(id = this.defaultId): Observable<Alert> {
|
onAlert(id = this.defaultId): Observable<Alert> {
|
||||||
return this.subject.asObservable();
|
return this.subject.asObservable();
|
||||||
@@ -37,6 +40,10 @@ export class AlertService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
error(message: string, title = 'Error!', options?: any): void {
|
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 }));
|
this.alert(new Alert({ ...options, type: AlertType.Error, message, title }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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="title">{{alert.title}}</span>
|
||||||
<span class="message">{{alert.message}}</span>
|
<span class="message">{{alert.message}}</span>
|
||||||
<fa-icon [icon]="faClose" (click)="removeAlert(alert)"></fa-icon>
|
<fa-icon [icon]="faClose" (click)="removeAlert(alert)"></fa-icon>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
transition: 0.25s ease;
|
transition: 0.25s ease;
|
||||||
animation: appear 0.5s ease;
|
animation: appear 0.5s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 4.5px 5.3px rgba(0, 0, 0, 0.121),
|
0 4.5px 5.3px rgba(0, 0, 0, 0.121),
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
|
|
||||||
.alert-warning {
|
.alert-warning {
|
||||||
background-color: #F3CC17 !important;
|
background-color: #F3CC17 !important;
|
||||||
|
color: #000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fadeOut {
|
.fadeOut {
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ export class AlertComponent implements OnInit, OnDestroy {
|
|||||||
this.alerts.push(alert);
|
this.alerts.push(alert);
|
||||||
this.alerts = this.alerts.reverse();
|
this.alerts = this.alerts.reverse();
|
||||||
|
|
||||||
|
if (alert.duration !== 0) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.removeAlert(alert);
|
this.removeAlert(alert);
|
||||||
}, alert.duration * 1000);
|
}, alert.duration * 1000);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +70,15 @@ export class AlertComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
classes.push(alertTypeClass[alert.type]);
|
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'); }
|
if (alert.isClosing) { classes.push('fadeOut'); }
|
||||||
|
|
||||||
return classes.join(' ');
|
return classes.join(' ');
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<h2>Create new user</h2>
|
<h2>Create new user</h2>
|
||||||
<button (click)="alert()">Make info</button>
|
|
||||||
<form (ngSubmit)="createUser()" #form="ngForm">
|
<form (ngSubmit)="createUser()" #form="ngForm">
|
||||||
<input name="username" [(ngModel)]="newUsername" placeholder="Username"><br>
|
<input name="username" [(ngModel)]="newUsername" placeholder="Username"><br>
|
||||||
<input name="password" [(ngModel)]="newPassword" type="password" placeholder="Password"><br>
|
<input name="password" [(ngModel)]="newPassword" type="password" placeholder="Password"><br>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { AfterContentInit, Component, OnDestroy, OnInit } from '@angular/core';
|
import { AfterContentInit, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { resolve } from 'dns';
|
||||||
import { APIService, UserType } from '../api.service';
|
import { APIService, UserType } from '../api.service';
|
||||||
import { AlertService } from '../_alert/alert.service';
|
import { AlertService } from '../_alert/alert.service';
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ export class AdminComponent implements AfterContentInit, OnDestroy {
|
|||||||
newType: UserType;
|
newType: UserType;
|
||||||
invitationCode: string;
|
invitationCode: string;
|
||||||
|
|
||||||
constructor(public api: APIService, private alertt: AlertService) { }
|
constructor(public api: APIService, private alert: AlertService) { }
|
||||||
|
|
||||||
ngAfterContentInit(): void {
|
ngAfterContentInit(): void {
|
||||||
this.api.showFilter = false;
|
this.api.showFilter = false;
|
||||||
@@ -27,11 +28,17 @@ export class AdminComponent implements AfterContentInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createUser(): Promise<void> {
|
async createUser(): Promise<void> {
|
||||||
this.invitationCode = await this.api.createUser(this.newUsername, this.newPassword, this.newType);
|
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) => {
|
||||||
alert() {
|
this.invitationCode = val;
|
||||||
this.alertt.info('This is a test from admin', 'Admin says');
|
this.alert.success(`User ${this.newUsername} created.`, 'Created');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { MqttService } from 'ngx-mqtt';
|
|||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { error } from 'protractor';
|
import { error } from 'protractor';
|
||||||
|
import { AlertService } from './_alert/alert.service';
|
||||||
|
|
||||||
export interface ILogin {
|
export interface ILogin {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -107,7 +108,7 @@ export class APIService {
|
|||||||
|
|
||||||
API_ENDPOINT = 'http://192.168.178.26:8040';
|
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 {
|
private mqttInit(): void {
|
||||||
// Connect with RabbitMQ after we received our user information
|
// Connect with RabbitMQ after we received our user information
|
||||||
@@ -120,13 +121,32 @@ export class APIService {
|
|||||||
password: this.user.brokerToken
|
password: this.user.brokerToken
|
||||||
});
|
});
|
||||||
|
|
||||||
this.mqtt.observe('/').subscribe(message => {
|
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) {
|
if (this.beats !== undefined) {
|
||||||
const obj = JSON.parse(message.payload.toString()) as IBeat;
|
|
||||||
this.beats.push(obj);
|
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.beatsEvent.next([obj]); // We just push one, so the map doesn't has to rebuild everything from scratch.
|
||||||
this.beatStats.totalBeats++;
|
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.getBeats();
|
||||||
await this.getBeatStats();
|
await this.getBeatStats();
|
||||||
this.loginEvent.next(true);
|
this.loginEvent.next(true);
|
||||||
|
this.alert.success('Login successful', 'Login', { duration: 2 });
|
||||||
resolve(token as ILogin);
|
resolve(token as ILogin);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -177,6 +198,9 @@ export class APIService {
|
|||||||
console.log('Response:', res);
|
console.log('Response:', res);
|
||||||
|
|
||||||
resolve(res.setupToken);
|
resolve(res.setupToken);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -331,6 +355,16 @@ export class APIService {
|
|||||||
return earthRadiusKm * c;
|
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]`
|
* Short form for `this.api.beats[this.api.beats.length - 1]`
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,8 +4,16 @@
|
|||||||
<ul class="navbar">
|
<ul class="navbar">
|
||||||
<li><a [routerLink]="['/dashboard']" routerLinkActive="router-link-active">Dashboard</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><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 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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-spacer"></div>
|
<div class="header-spacer"></div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { faUser } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { APIService } from './api.service';
|
import { APIService } from './api.service';
|
||||||
import { AlertService } from './_alert/alert.service';
|
import { AlertService } from './_alert/alert.service';
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ export class AppComponent implements OnInit{
|
|||||||
title = 'Livebeat';
|
title = 'Livebeat';
|
||||||
|
|
||||||
showOverlay = false;
|
showOverlay = false;
|
||||||
|
faUser = faUser;
|
||||||
|
|
||||||
constructor(public api: APIService, private alert: AlertService) {
|
constructor(public api: APIService, private alert: AlertService) {
|
||||||
this.api.fetchingDataEvent.subscribe(status => {
|
this.api.fetchingDataEvent.subscribe(status => {
|
||||||
@@ -20,8 +22,7 @@ export class AppComponent implements OnInit{
|
|||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
await this.api.login('admin', '$1KDaNCDlyXAOg');
|
await this.api.login('admin', '$1KDaNCDlyXAOg');
|
||||||
this.alert.success('This is just a test', 'Test');
|
this.alert.error('Audio test');
|
||||||
this.alert.error('Requested user doesn\'t exist', 'Not found');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
|
|
||||||
this.api.beatsEvent.subscribe(beats => {
|
this.api.beatsEvent.subscribe(beats => {
|
||||||
// Only reset array if this is not an update.
|
// Only reset array if this is not an update.
|
||||||
if (beats.length !== 1) {
|
if (beats.length > 1) {
|
||||||
this.batteryLineChartData[0].data = [];
|
this.batteryLineChartData[0].data = [];
|
||||||
this.batteryLineChartLabels = [];
|
this.batteryLineChartLabels = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,8 +159,6 @@ export class MapComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildMap(isUpdate: boolean, maxAccuracy: number = 30): void {
|
buildMap(isUpdate: boolean, maxAccuracy: number = 30): void {
|
||||||
console.log(isUpdate);
|
|
||||||
|
|
||||||
// If this is an update don't rebuild entire map.
|
// If this is an update don't rebuild entire map.
|
||||||
if (!isUpdate) {
|
if (!isUpdate) {
|
||||||
if (this.api.beats.length === 0) {
|
if (this.api.beats.length === 0) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<h2 *ngIf="showDevices">Devices</h2>
|
<h2 *ngIf="showDevices">Devices</h2>
|
||||||
<ul class="phoneListing" *ngIf="showDevices">
|
<ul class="phoneListing" *ngIf="showDevices">
|
||||||
<li *ngFor="let phone of this.api.phones">
|
<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>
|
<p>{{phone.modelName}}</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
background-color: $darker;
|
background-color: $darker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.offline {
|
||||||
|
color: #ff6464
|
||||||
|
}
|
||||||
|
|
||||||
.lastBeat {
|
.lastBeat {
|
||||||
font-weight: lighter;
|
font-weight: lighter;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
templateUrl: './user.component.html',
|
templateUrl: './user.component.html',
|
||||||
styleUrls: ['./user.component.scss']
|
styleUrls: ['./user.component.scss']
|
||||||
})
|
})
|
||||||
export class UserComponent implements AfterContentInit, OnDestroy {
|
export class UserComponent implements AfterContentInit, OnDestroy, OnInit {
|
||||||
|
|
||||||
lastLogin: string;
|
lastLogin: string;
|
||||||
lastBeats: Map<string, string> = new Map<string, string>();
|
lastBeats: Map<string, string> = new Map<string, string>();
|
||||||
@@ -60,7 +60,12 @@ export class UserComponent implements AfterContentInit, OnDestroy {
|
|||||||
|
|
||||||
ngAfterContentInit(): void {
|
ngAfterContentInit(): void {
|
||||||
this.api.showFilter = false;
|
this.api.showFilter = false;
|
||||||
console.log(this.api.showFilter);
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.api.hasSession()) {
|
||||||
|
this.api.getPhones();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
|||||||
BIN
frontend/src/assets/sounds/error.mp3
Normal file
BIN
frontend/src/assets/sounds/error.mp3
Normal file
Binary file not shown.
Reference in New Issue
Block a user