diff --git a/web-app/src/app/about/about.component.html b/web-app/src/app/about/about.component.html index 47a1e04a3..c2d5ee18e 100644 --- a/web-app/src/app/about/about.component.html +++ b/web-app/src/app/about/about.component.html @@ -1,59 +1,67 @@ -
-

About MAGE Server {{mageVersion.major}}.{{mageVersion.minor}}.{{mageVersion.micro}}

-

- MAGE is a dynamic, secure, mobile situational awareness and field data collection platform that supports - low-bandwidth and disconnected users. MAGE can integrate with existing command centers and common operating - pictures or stand alone as a complete, mission-ready solution. The MAGE mobile app on iOS and Android - allows agents in the field to create and share geo-tagged observations with attached photos, videos, - audio, and form data. MAGE's data collection forms are easily tailored to suit any team and mission with - custom form fields and map symbologies. In addition to data collection, the MAGE mobile app can optionally - report field agents' locations at regular intervals to the MAGE server. The MAGE server's web app provides - a common operating picture of field agents' observations and latest reported locations. -

-

Mobile Applications

- -
-
- - -
-
-

API

-

Browse and try the MAGE API live with Swagger UI.

-

Important: Swagger interactive documentation will modify MAGE data via API calls; please be - careful with POST/PUT/DELETE operations.

-
-

System

- + +
+ + + About
-

Acknowledgements

-
-
-

Tech Stack

-

The MAGE server is built on NodeJS, with data stored in MongoDB via mongoose. Routing provided with express, and authentication with passport.

-

The front end maps are powered by Leaflet. The responsive - layouts and buttons are styled by a modified version of Bootstrap. The whole front end is tied together by AngularJS.

+ + +
+
+
About
+

+ MAGE is a dynamic, secure, mobile situational awareness and field data collection platform that supports + low-bandwidth and disconnected users. MAGE can integrate with existing command centers and common operating + pictures or stand alone as a complete, mission-ready solution. The MAGE mobile app on iOS and Android + allows agents in the field to create and share geo-tagged observations with attached photos, videos, + audio, and form data. MAGE's data collection forms are easily tailored to suit any team and mission with + custom form fields and map symbologies. In addition to data collection, the MAGE mobile app can optionally + report field agents' locations at regular intervals to the MAGE server. The MAGE server's web app provides + a common operating picture of field agents' observations and latest reported locations. +

+ + -
-

Iconography

-

The icons used throughout the app come from Font Awesome

+
+ +
+
API
+
+ Browse and try the MAGE API live with Swagger UI. Swagger interactive documentation will modify MAGE data via API calls; please be careful with POST/PUT/DELETE operations.
+ +
+
System
+
Server Version {{mageVersion.major}}.{{mageVersion.minor}}.{{mageVersion.micro}}
+
Node Version {{nodeVersion}}
+
MongoDB Version {{mongoVersion}}
+
+ +
+
Acknowledgements
+
The MAGE is built with the MEAN stack; + MongoDB, + Express, + Angular and + NodeJS. +
+ +
Authentication is locked down by + Passport +
+ +
Maps are powered by + Leaflet +
+
+
\ No newline at end of file diff --git a/web-app/src/app/about/about.component.scss b/web-app/src/app/about/about.component.scss index e69de29bb..933ea63b3 100644 --- a/web-app/src/app/about/about.component.scss +++ b/web-app/src/app/about/about.component.scss @@ -0,0 +1,37 @@ +@use '@angular/material' as mat; +@import "variables.scss"; + +.content { + width: 50vw; + margin: 0 auto; + padding: 32px 0; + display: flex; + flex-direction: column; + gap: 32px; +} + +.mat-h1 { + color: mat.get-color-from-palette($app-primary); +} + +.container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.title { + margin-left: 16px; +} + +.app-store { + display: flex; + flex-direction: row; + gap: 16px; + align-items: center; +} + +.app-store-icon { + height: 48px; +} \ No newline at end of file diff --git a/web-app/src/app/about/about.component.ts b/web-app/src/app/about/about.component.ts index 467967886..856add689 100644 --- a/web-app/src/app/about/about.component.ts +++ b/web-app/src/app/about/about.component.ts @@ -1,5 +1,6 @@ import { Component, Inject, OnInit } from '@angular/core'; import { ApiService } from '../api/api.service'; +import { Router } from '@angular/router'; @Component({ selector: 'about', @@ -19,6 +20,7 @@ export class AboutComponent implements OnInit { mongoVersion: string constructor( + private router: Router, @Inject(ApiService) public apiService: ApiService ) {} @@ -31,4 +33,8 @@ export class AboutComponent implements OnInit { this.mongoVersion = api.environment.mongodbVersion; }) } + + onBack(): void { + this.router.navigate(['map']); + } } diff --git a/web-app/src/app/app.module.ts b/web-app/src/app/app.module.ts index 96d6f3767..a75ff963c 100644 --- a/web-app/src/app/app.module.ts +++ b/web-app/src/app/app.module.ts @@ -171,10 +171,12 @@ import { PollingIntervalComponent } from './preferences/polling-interval/polling import { TimeFormatComponent } from './preferences/time-format/time-format.component'; import { TimeZoneComponent } from './preferences/time-zone/time-zone.component'; import { CoordinateSystemComponent } from './preferences/coordinate-system/coordinate-system.component'; +import { AboutComponent } from './about/about.component'; @NgModule({ declarations: [ AppComponent, + AboutComponent, NavigationComponent, ZoomComponent, AddObservationComponent, diff --git a/web-app/src/app/event/event.service.ts b/web-app/src/app/event/event.service.ts index a1dca848d..edfdf9983 100644 --- a/web-app/src/app/event/event.service.ts +++ b/web-app/src/app/event/event.service.ts @@ -69,7 +69,8 @@ export class EventService { } onEventChanged(event: any) { - event.added.forEach((added: any) => { + const { added = [], removed = [], foo = [] } = event + added.forEach((added: any) => { if (!this.eventsById[added.id]) { this.eventsById[added.id] = (JSON.parse(JSON.stringify(added))); @@ -83,7 +84,7 @@ export class EventService { this.fetchFeeds(added); }) - event.removed.forEach((removed: any) => { + removed.forEach((removed: any) => { this.observationsChanged({ removed: Object.values(this.eventsById[removed.id].filteredObservationsById) }); this.usersChanged({ removed: Object.values(this.eventsById[removed.id].filteredUsersById) }); this.layersChanged({ removed: Object.values(this.eventsById[removed.id].layersById) }, removed); diff --git a/web-app/src/app/navigation/navigation.component.html b/web-app/src/app/navigation/navigation.component.html index 8991f3010..c04d49683 100644 --- a/web-app/src/app/navigation/navigation.component.html +++ b/web-app/src/app/navigation/navigation.component.html @@ -1,7 +1,7 @@
-
- diff --git a/web-app/src/app/navigation/navigation.component.scss b/web-app/src/app/navigation/navigation.component.scss index a0c54da8c..ad07672e0 100644 --- a/web-app/src/app/navigation/navigation.component.scss +++ b/web-app/src/app/navigation/navigation.component.scss @@ -6,9 +6,17 @@ width: 100%; display: flex; flex-direction: row; + align-items: center; justify-content: space-between; } +.menu { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + .title { margin-left: 16px; } diff --git a/web-app/src/app/navigation/navigation.component.ts b/web-app/src/app/navigation/navigation.component.ts index fb455586b..523a6d052 100644 --- a/web-app/src/app/navigation/navigation.component.ts +++ b/web-app/src/app/navigation/navigation.component.ts @@ -69,10 +69,6 @@ export class NavigationComponent implements OnInit, OnDestroy { this.eventService.destroy(); } - openMenu(): void { - - } - toggleFeed(): void { this.onFeedToggle.emit() } diff --git a/web-app/src/app/preferences/preferences.component.html b/web-app/src/app/preferences/preferences.component.html index b35cd04f4..85be28411 100644 --- a/web-app/src/app/preferences/preferences.component.html +++ b/web-app/src/app/preferences/preferences.component.html @@ -26,12 +26,12 @@ -
+
info
About
-
+
logout
Logout
diff --git a/web-app/src/app/preferences/preferences.component.ts b/web-app/src/app/preferences/preferences.component.ts index 3ec7940d0..981477942 100644 --- a/web-app/src/app/preferences/preferences.component.ts +++ b/web-app/src/app/preferences/preferences.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { UserService } from '../user/user.service'; +import { Router } from '@angular/router'; @Component({ selector: 'preferences', @@ -10,10 +11,19 @@ export class PreferencesComponent implements OnInit { user: any constructor( + private router: Router, private userService: UserService ) {} ngOnInit(): void { this.user = this.userService.myself } + + onAbout(): void { + this.router.navigate(['about']); + } + + onLogout(): void { + this.userService.logout() + } } \ No newline at end of file diff --git a/web-app/src/app/routing.module.ts b/web-app/src/app/routing.module.ts index 3a5d5c11f..512db7f38 100644 --- a/web-app/src/app/routing.module.ts +++ b/web-app/src/app/routing.module.ts @@ -6,6 +6,7 @@ import { AuthorizeComponent } from './authentication/authorize.component'; import { MageComponent } from './mage/mage.component'; import { ApiResolver } from './api/api.resolver'; import { UserResolver } from './authentication/user.resolver'; +import { AboutComponent } from './about/about.component'; const appRoutes: Routes = [{ path: '', @@ -34,6 +35,9 @@ const appRoutes: Routes = [{ resolve: { user: UserResolver } +},{ + path: 'about', + component: AboutComponent }]; @NgModule({ diff --git a/web-app/src/app/user/user.service.ts b/web-app/src/app/user/user.service.ts index 532373b2f..fe10ada2f 100644 --- a/web-app/src/app/user/user.service.ts +++ b/web-app/src/app/user/user.service.ts @@ -1,6 +1,8 @@ -import { HttpClient } from '@angular/common/http' +import { HttpClient, HttpEvent, HttpResponse } from '@angular/common/http' import { Injectable } from '@angular/core' +import { Router } from '@angular/router' import { Observable, Subject } from 'rxjs' +import { LocalStorageService } from '../http/local-storage.service' @Injectable({ providedIn: 'root' @@ -10,7 +12,11 @@ export class UserService { myself: any amAdmin: boolean - constructor(private httpClient: HttpClient) { } + constructor( + private router: Router, + private httpClient: HttpClient, + private localStorageService: LocalStorageService + ) { } signin(username: string, password: string): Observable { const body = { @@ -100,4 +106,21 @@ export class UserService { var recentEventIds = this.myself.recentEventIds; return recentEventIds.length > 0 ? recentEventIds[0] : null; } + + logout() { + const observable = this.httpClient.post('/api/logout', null, { responseType: 'text' }) + observable.subscribe(() => { + console.log('logged out user') + this.clearUser(); + this.router.navigate(['landing']); + }) + + return observable; + } + + private clearUser() { + this.myself = null; + this.amAdmin = null; + this.localStorageService.removeToken(); + } } \ No newline at end of file