Frontend auth PoC
This commit is contained in:
parent
7397819c00
commit
097aa485ff
File diff suppressed because it is too large
Load Diff
|
@ -21,7 +21,7 @@
|
||||||
"@angular/platform-browser-dynamic": "^15.1.5",
|
"@angular/platform-browser-dynamic": "^15.1.5",
|
||||||
"@angular/router": "^15.1.5",
|
"@angular/router": "^15.1.5",
|
||||||
"@angular/service-worker": "^15.1.5",
|
"@angular/service-worker": "^15.1.5",
|
||||||
"@asymmetrik/ngx-leaflet": "^8.1.0",
|
"@asymmetrik/ngx-leaflet": "^15.0.1",
|
||||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||||
"@ngx-translate/core": "^14.0.0",
|
"@ngx-translate/core": "^14.0.0",
|
||||||
"@ngx-translate/http-loader": "^7.0.0",
|
"@ngx-translate/http-loader": "^7.0.0",
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
"leaflet": "^1.7.1",
|
"leaflet": "^1.7.1",
|
||||||
"leaflet.locatecontrol": "^0.76.0",
|
"leaflet.locatecontrol": "^0.76.0",
|
||||||
"ngx-bootstrap": "^10.1.0",
|
"ngx-bootstrap": "^10.1.0",
|
||||||
"ngx-toastr": "^14.2.1",
|
"ngx-toastr": "^16.0.2",
|
||||||
"rxjs": "~7.4.0",
|
"rxjs": "~7.4.0",
|
||||||
"sweetalert2": "^11.3.4",
|
"sweetalert2": "^11.3.4",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"/api": {
|
"/api": {
|
||||||
"target": "http://127.0.0.1:8080",
|
"target": "http://allertavvf.test/",
|
||||||
"secure": false,
|
"secure": false,
|
||||||
"changeOrigin": true
|
"changeOrigin": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<img class="owner_image" alt="VVF" src="./api/owner_image">
|
<!-- <img class="owner_image" alt="VVF" src="./api/owner_image"> -->
|
|
@ -10,10 +10,10 @@ export class AuthorizeGuard implements CanActivate {
|
||||||
constructor(private authService: AuthService, private router: Router) {
|
constructor(private authService: AuthService, private router: Router) {
|
||||||
}
|
}
|
||||||
|
|
||||||
canActivate(
|
checkAuthAndRedirect(
|
||||||
route: ActivatedRouteSnapshot,
|
route: ActivatedRouteSnapshot,
|
||||||
state: RouterStateSnapshot
|
state: RouterStateSnapshot
|
||||||
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
): boolean {
|
||||||
console.log(this.authService, route, state);
|
console.log(this.authService, route, state);
|
||||||
if(this.authService.profile === undefined) {
|
if(this.authService.profile === undefined) {
|
||||||
console.log("not logged in");
|
console.log("not logged in");
|
||||||
|
@ -23,4 +23,19 @@ export class AuthorizeGuard implements CanActivate {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canActivate(
|
||||||
|
route: ActivatedRouteSnapshot,
|
||||||
|
state: RouterStateSnapshot
|
||||||
|
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||||
|
if(this.authService.authLoaded) {
|
||||||
|
return this.checkAuthAndRedirect(route, state);
|
||||||
|
} else {
|
||||||
|
return new Observable<boolean>((observer) => {
|
||||||
|
this.authService.authChanged.subscribe({
|
||||||
|
next: () => { observer.next(this.checkAuthAndRedirect(route, state)); }
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,72 +1,30 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpErrorResponse } from '@angular/common/http';
|
||||||
import { AuthService } from '../_services/auth.service';
|
import { AuthService } from '../_services/auth.service';
|
||||||
import { BehaviorSubject, Observable, throwError } from 'rxjs';
|
import { Observable, throwError } from 'rxjs';
|
||||||
import { catchError, filter, switchMap, take } from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthInterceptor implements HttpInterceptor {
|
export class AuthInterceptor implements HttpInterceptor {
|
||||||
private isRefreshing = false;
|
constructor(/*private auth: AuthService*/) { }
|
||||||
private refreshTokenSubject: BehaviorSubject<string|undefined> = new BehaviorSubject<string|undefined>(undefined);
|
|
||||||
|
|
||||||
constructor(private auth: AuthService) { }
|
//TODO: fix interceptor and logout (client-side only) if 401 error
|
||||||
|
|
||||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<Object>> {
|
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<Object>> {
|
||||||
const token = this.auth.getToken();
|
return next.handle(req);
|
||||||
let authReq = this.addHeaders(req, token);
|
/*
|
||||||
|
return next.handle(req).pipe(catchError(error => {
|
||||||
return next.handle(authReq).pipe(catchError(error => {
|
console.log(error);
|
||||||
if (error instanceof HttpErrorResponse && !authReq.url.includes('login')) {
|
if (error instanceof HttpErrorResponse && !req.url.includes('login') && !req.url.includes('me') && !req.url.includes('logout')) {
|
||||||
if(error.status === 400) {
|
if(error.status === 400) {
|
||||||
return this.handle400Error(authReq, next);
|
this.auth.logout();
|
||||||
|
return throwError(() => error);
|
||||||
} else if (error.status === 401) {
|
} else if (error.status === 401) {
|
||||||
this.auth.logout();
|
this.auth.logout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return throwError(() => new Error(error));
|
return throwError(() => error);
|
||||||
}));
|
}));
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private handle400Error(request: HttpRequest<any>, next: HttpHandler) {
|
|
||||||
if (!this.isRefreshing) {
|
|
||||||
this.isRefreshing = true;
|
|
||||||
this.refreshTokenSubject.next(undefined);
|
|
||||||
return this.auth.refreshToken().pipe(
|
|
||||||
switchMap((token: string) => {
|
|
||||||
this.isRefreshing = false;
|
|
||||||
this.refreshTokenSubject.next(token);
|
|
||||||
|
|
||||||
return next.handle(this.addHeaders(request, token));
|
|
||||||
}),
|
|
||||||
catchError((err) => {
|
|
||||||
this.isRefreshing = false;
|
|
||||||
this.auth.logout();
|
|
||||||
return throwError(() => new Error(err));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return this.refreshTokenSubject.pipe(
|
|
||||||
filter(token => token !== undefined),
|
|
||||||
take(1),
|
|
||||||
switchMap((token) => {
|
|
||||||
return next.handle(this.addHeaders(request, token));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private addHeaders(request: HttpRequest<any>, token: string|undefined) {
|
|
||||||
if (typeof token === 'string' && token.length > 10) {
|
|
||||||
const headers = new HttpHeaders({
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'Authorization': `Bearer ${token}`
|
|
||||||
});
|
|
||||||
return request.clone({ headers });
|
|
||||||
} else {
|
|
||||||
const headers = new HttpHeaders({
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
|
||||||
});
|
|
||||||
return request.clone({ headers });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,15 +20,15 @@
|
||||||
<owner-image></owner-image>
|
<owner-image></owner-image>
|
||||||
<div class="text-center" *ngIf="auth.profile.hasRole('SUPER_EDITOR')">
|
<div class="text-center" *ngIf="auth.profile.hasRole('SUPER_EDITOR')">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button type="button" class="btn btn-danger" (click)="addAlertFull()" [disabled]="!api?.availableUsers || api.availableUsers! < 5 || alertLoading">
|
<button type="button" class="btn btn-danger" (click)="addAlertFull()" [disabled]="!api.availableUsers || api.availableUsers! < 5 || alertLoading">
|
||||||
🚒 Richiedi squadra completa
|
🚒 Richiedi squadra completa
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-warning" (click)="addAlertSupport()" [disabled]="!api?.availableUsers || api.availableUsers! < 2 || alertLoading">
|
<button type="button" class="btn btn-warning" (click)="addAlertSupport()" [disabled]="!api.availableUsers || api.availableUsers! < 2 || alertLoading">
|
||||||
Richiedi squadra di supporto 🧯
|
Richiedi squadra di supporto 🧯
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-table [sourceType]="'list'" (changeAvailability)="changeAvailibility($event.newState, $event.user)" #table></app-table>
|
<!-- <app-table [sourceType]="'list'" (changeAvailability)="changeAvailibility($event.newState, $event.user)" #table></app-table> -->
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button (click)="requestTelegramToken()" class="btn btn-md btn-success mt-3">{{ 'list.connect_telegram_bot'|translate }}</button>
|
<button (click)="requestTelegramToken()" class="btn btn-md btn-success mt-3">{{ 'list.connect_telegram_bot'|translate }}</button>
|
||||||
</div>
|
</div>
|
|
@ -32,7 +32,6 @@ export class ListComponent implements OnInit, OnDestroy {
|
||||||
private modalService: BsModalService,
|
private modalService: BsModalService,
|
||||||
private translate: TranslateService
|
private translate: TranslateService
|
||||||
) {
|
) {
|
||||||
this.loadAvailability();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAvailability() {
|
loadAvailability() {
|
||||||
|
@ -118,13 +117,18 @@ export class ListComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
/*
|
||||||
this.loadAvailabilityInterval = setInterval(() => {
|
this.loadAvailabilityInterval = setInterval(() => {
|
||||||
console.log("Refreshing availability...");
|
console.log("Refreshing availability...");
|
||||||
this.loadAvailability();
|
this.loadAvailability();
|
||||||
}, 10000);
|
}, 10000);
|
||||||
this.auth.authChanged.subscribe({
|
this.auth.authChanged.subscribe({
|
||||||
next: () => this.loadAvailability()
|
next: () => {
|
||||||
|
this.loadAvailability();
|
||||||
|
this.loadAvailability();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
|
|
@ -20,16 +20,6 @@ export class ApiClientService {
|
||||||
return this.apiRoot + endpoint;
|
return this.apiRoot + endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public dataToParams(data: any): string {
|
|
||||||
return Object.keys(data).reduce(function (params, key) {
|
|
||||||
if(typeof data[key] === 'object') {
|
|
||||||
data[key] = JSON.stringify(data[key]);
|
|
||||||
}
|
|
||||||
params.set(key, data[key]);
|
|
||||||
return params;
|
|
||||||
}, new URLSearchParams()).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public get(endpoint: string, data: any = {}) {
|
public get(endpoint: string, data: any = {}) {
|
||||||
return new Promise<any>((resolve, reject) => {
|
return new Promise<any>((resolve, reject) => {
|
||||||
this.http.get(this.apiEndpoint(endpoint), {
|
this.http.get(this.apiEndpoint(endpoint), {
|
||||||
|
@ -43,7 +33,7 @@ export class ApiClientService {
|
||||||
|
|
||||||
public post(endpoint: string, data: any = {}) {
|
public post(endpoint: string, data: any = {}) {
|
||||||
return new Promise<any>((resolve, reject) => {
|
return new Promise<any>((resolve, reject) => {
|
||||||
this.http.post(this.apiEndpoint(endpoint), this.dataToParams(data)).subscribe({
|
this.http.post(this.apiEndpoint(endpoint), data).subscribe({
|
||||||
next: (v) => resolve(v),
|
next: (v) => resolve(v),
|
||||||
error: (e) => reject(e)
|
error: (e) => reject(e)
|
||||||
});
|
});
|
||||||
|
@ -52,7 +42,7 @@ export class ApiClientService {
|
||||||
|
|
||||||
public put(endpoint: string, data: any = {}) {
|
public put(endpoint: string, data: any = {}) {
|
||||||
return new Promise<any>((resolve, reject) => {
|
return new Promise<any>((resolve, reject) => {
|
||||||
this.http.put(this.apiEndpoint(endpoint), this.dataToParams(data)).subscribe({
|
this.http.put(this.apiEndpoint(endpoint), data).subscribe({
|
||||||
next: (v) => resolve(v),
|
next: (v) => resolve(v),
|
||||||
error: (e) => reject(e)
|
error: (e) => reject(e)
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { ApiClientService } from './api-client.service';
|
import { ApiClientService } from './api-client.service';
|
||||||
import { Observable, Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import jwt_decode from 'jwt-decode';
|
|
||||||
|
|
||||||
export interface LoginResponse {
|
export interface LoginResponse {
|
||||||
loginOk: boolean;
|
loginOk: boolean;
|
||||||
|
@ -14,59 +13,37 @@ export interface LoginResponse {
|
||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
public profile: any = undefined;
|
public profile: any = undefined;
|
||||||
private access_token: string | undefined = undefined;
|
|
||||||
public authChanged = new Subject<void>();
|
public authChanged = new Subject<void>();
|
||||||
|
public authLoaded = false;
|
||||||
|
|
||||||
public loadProfile() {
|
public loadProfile() {
|
||||||
try{
|
console.log("Loading profile data...");
|
||||||
console.log("Loading profile", this.access_token);
|
return new Promise<void>((resolve, reject) => {
|
||||||
let now = Date.now().valueOf() / 1000;
|
this.api.post("me").then((data: any) => {
|
||||||
(window as any).jwt_decode = jwt_decode;
|
this.profile = data;
|
||||||
if(typeof(this.access_token) !== "string") return;
|
|
||||||
let decoded: any = jwt_decode(this.access_token);
|
this.profile.hasRole = (role: string) => {
|
||||||
if (typeof decoded.exp !== 'undefined' && decoded.exp < now) {
|
return true;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
if (typeof decoded.nbf !== 'undefined' && decoded.nbf > now) {
|
this.authChanged.next();
|
||||||
return false;
|
resolve();
|
||||||
}
|
}).catch((e) => {
|
||||||
this.profile = decoded.user_info;
|
console.error(e);
|
||||||
|
this.profile = undefined;
|
||||||
this.profile.hasRole = (role: string) => {
|
reject();
|
||||||
return Object.values(this.profile.roles).includes(role);
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
console.log(this.profile);
|
|
||||||
this.authChanged.next();
|
|
||||||
return true;
|
|
||||||
} catch(e) {
|
|
||||||
console.error(e);
|
|
||||||
this.removeToken();
|
|
||||||
this.profile = undefined;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private api: ApiClientService, private router: Router) {
|
constructor(private api: ApiClientService, private router: Router) {
|
||||||
if(localStorage.getItem("access_token") !== null) {
|
this.loadProfile().then(() => {
|
||||||
this.access_token = localStorage.getItem("access_token") as string;
|
console.log("User is authenticated");
|
||||||
this.loadProfile();
|
}).catch(() => {
|
||||||
}
|
console.log("User is not logged in");
|
||||||
}
|
}).finally(() => {
|
||||||
|
this.authLoaded = true;
|
||||||
public setToken(value: string) {
|
});
|
||||||
localStorage.setItem("access_token", value);
|
|
||||||
this.access_token = value;
|
|
||||||
this.loadProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public getToken(): string | undefined {
|
|
||||||
return this.access_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
private removeToken() {
|
|
||||||
this.access_token = '';
|
|
||||||
localStorage.removeItem("access_token");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public isAuthenticated() {
|
public isAuthenticated() {
|
||||||
|
@ -75,34 +52,40 @@ export class AuthService {
|
||||||
|
|
||||||
public login(username: string, password: string) {
|
public login(username: string, password: string) {
|
||||||
return new Promise<LoginResponse>((resolve) => {
|
return new Promise<LoginResponse>((resolve) => {
|
||||||
this.api.post("login", {
|
this.api.get("csrf-cookie").then((data: any) => {
|
||||||
username: username,
|
this.api.post("login", {
|
||||||
password: password
|
username: username,
|
||||||
}).then((data: any) => {
|
password: password
|
||||||
console.log(data);
|
}).then((data: any) => {
|
||||||
this.setToken(data.access_token);
|
this.loadProfile().then(() => {
|
||||||
console.log("Access token", data);
|
resolve({
|
||||||
resolve({
|
loginOk: true,
|
||||||
loginOk: true,
|
message: data.message
|
||||||
message: data.message
|
});
|
||||||
});
|
}).catch(() => {
|
||||||
}).catch((err) => {
|
resolve({
|
||||||
let error_message = "";
|
loginOk: false,
|
||||||
if(err.status === 401) {
|
message: "Unknown error"
|
||||||
error_message = err.error.message;
|
});
|
||||||
} else if (err.status === 400) {
|
});
|
||||||
let error_messages = err.error.errors;
|
}).catch((err) => {
|
||||||
error_message = error_messages.map((val: any) => {
|
let error_message = "";
|
||||||
return `${val.msg} in ${val.param}`;
|
if(err.status === 401) {
|
||||||
}).join(" & ");
|
error_message = err.error.message;
|
||||||
} else if (err.status === 500) {
|
} else if (err.status === 400) {
|
||||||
error_message = "Server error";
|
let error_messages = err.error.errors;
|
||||||
} else {
|
error_message = error_messages.map((val: any) => {
|
||||||
error_message = "Unknown error";
|
return `${val.msg} in ${val.param}`;
|
||||||
}
|
}).join(" & ");
|
||||||
resolve({
|
} else if (err.status === 500) {
|
||||||
loginOk: false,
|
error_message = "Server error";
|
||||||
message: error_message
|
} else {
|
||||||
|
error_message = "Unknown error";
|
||||||
|
}
|
||||||
|
resolve({
|
||||||
|
loginOk: false,
|
||||||
|
message: error_message
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -110,52 +93,29 @@ export class AuthService {
|
||||||
|
|
||||||
public impersonate(user_id: number): Promise<number> {
|
public impersonate(user_id: number): Promise<number> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
console.log("final", user_id);
|
resolve(0);
|
||||||
this.api.post("impersonate", {
|
|
||||||
user_id: user_id
|
|
||||||
}).then((response) => {
|
|
||||||
this.setToken(response.access_token);
|
|
||||||
resolve(user_id);
|
|
||||||
}).catch((err) => {
|
|
||||||
reject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public stop_impersonating(): Promise<number> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.api.post("stop_impersonating").then((response) => {
|
|
||||||
this.setToken(response.access_token);
|
|
||||||
resolve(response.user_id);
|
|
||||||
}).catch((err) => {
|
|
||||||
reject();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public logout(routerDestination?: string[] | undefined) {
|
public logout(routerDestination?: string[] | undefined) {
|
||||||
|
this.api.post("logout").then((data: any) => {
|
||||||
|
this.profile = undefined;
|
||||||
|
if(routerDestination === undefined) {
|
||||||
|
routerDestination = ["login", "list"];
|
||||||
|
}
|
||||||
|
this.router.navigate(routerDestination);
|
||||||
|
});
|
||||||
|
/*
|
||||||
if(this.profile.impersonating_user) {
|
if(this.profile.impersonating_user) {
|
||||||
this.stop_impersonating().then((user_id) => {
|
this.stop_impersonating().then((user_id) => {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.removeToken();
|
|
||||||
this.profile = undefined;
|
this.profile = undefined;
|
||||||
if(routerDestination === undefined) {
|
if(routerDestination === undefined) {
|
||||||
routerDestination = ["login", "list"];
|
routerDestination = ["login", "list"];
|
||||||
}
|
}
|
||||||
this.router.navigate(routerDestination);
|
this.router.navigate(routerDestination);
|
||||||
}
|
}
|
||||||
}
|
*/
|
||||||
|
|
||||||
public refreshToken() {
|
|
||||||
return new Observable<string>((observer) => {
|
|
||||||
this.api.post("refreshToken").then((data: any) => {
|
|
||||||
this.setToken(data.token);
|
|
||||||
observer.next(data.token);
|
|
||||||
observer.complete();
|
|
||||||
}).catch((err) => {
|
|
||||||
observer.error(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ export class AppComponent {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
this.loadAlertsInterval = setInterval(() => {
|
this.loadAlertsInterval = setInterval(() => {
|
||||||
console.log("Refreshing alerts...");
|
console.log("Refreshing alerts...");
|
||||||
this.loadAlerts();
|
this.loadAlerts();
|
||||||
|
@ -56,6 +57,7 @@ export class AppComponent {
|
||||||
this.api.alertsChanged.subscribe(() => {
|
this.api.alertsChanged.subscribe(() => {
|
||||||
this.loadAlerts();
|
this.loadAlerts();
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
openAlert(id: number) {
|
openAlert(id: number) {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
|
||||||
@import "~bootstrap/scss/bootstrap.scss";
|
@import "node_modules/bootstrap/dist/css/bootstrap.min.css";
|
||||||
@import "~@fortawesome/fontawesome-free/css/all.css";
|
@import "node_modules/@fortawesome/fontawesome-free/css/all.css";
|
||||||
@import '~ngx-toastr/toastr';
|
@import 'node_modules/ngx-toastr/toastr.css';
|
||||||
|
|
||||||
//Leaving this here because in component.scss it doesn't work really well
|
//Leaving this here because in component.scss it doesn't work really well
|
||||||
@import '~ngx-bootstrap/datepicker/bs-datepicker.scss';
|
@import 'node_modules/ngx-bootstrap/datepicker/bs-datepicker.scss';
|
||||||
@import '~leaflet/dist/leaflet.css';
|
@import 'node_modules/leaflet/dist/leaflet.css';
|
||||||
@import '~leaflet.locatecontrol/dist/L.Control.Locate.min.css';
|
@import 'node_modules/leaflet.locatecontrol/dist/L.Control.Locate.min.css';
|
||||||
|
|
||||||
.fa {
|
.fa {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
Loading…
Reference in New Issue