import {Injectable} from '@angular/core'; import {HttpClient, HttpErrorResponse} from '@angular/common/http'; import {MatTableDataSource} from '@angular/material'; import {Observable} from 'rxjs/Observable'; import {BehaviorSubject} from 'rxjs/BehaviorSubject'; import {Subject} from 'rxjs/Subject'; import {CookieService} from 'ngx-cookie-service'; import {StompConfig, StompRService} from '@stomp/ng2-stompjs'; import {Message} from '@stomp/stompjs'; import {SnackBarService} from './snackbar.service'; import {IdpService} from './idp.service'; import * as t from './types/types.module'; import { StateAPIResult as SAR } from './types/types.module'; @Injectable() export class UserService { private initialized = false; private loggedIn: boolean = false; private loggedIn$ = new BehaviorSubject(false); private sar: SAR; private sar$ = > new BehaviorSubject(new Object); private sshKeys: t.SSHKey[] = new Array(); private sshKeys$ = new BehaviorSubject(this.sshKeys); private userState: t.UserState; private userState$ = > new BehaviorSubject(new Object); private messages: string[] = []; public groupSelector = (state: SAR) => state ? state.user ? state.user.groups : [] : []; public userSelector = (state: SAR) => state ? state.user : null; public serviceSelector = (state: SAR) => state ? state.services : []; constructor( private cookieService: CookieService, private http: HttpClient, private snackBar: SnackBarService, private idpService: IdpService, private _stompService: StompRService, ) { this.connect(); } // PRIVATE API private disconnect(): void { this.loggedIn = false; this.sshKeys$.complete(); this.loggedIn$.next(this.loggedIn); } private connect(): void { this.subscribeState().subscribe( (state: SAR) => { console.log('State:', state); // update users const user = this.userSelector(state); if (user) { this.connectLiveUpdates(user); this.sshKeys = user.ssh_keys; } else { this.sshKeys = []; } this.sshKeys$.next(this.sshKeys); }, this.errorLogger, () => console.log('sar$ is complete'), ); this.subscribeLoggedIn().subscribe( (loggedIn: boolean) => { if (loggedIn) { if (!this.initialized) { this.snackBar.open('Logged in'); } this.initialized = true; } if (!loggedIn && this.initialized) { console.log('Logging out'); this._stompService.disconnect(); this.sar$.complete(); this.userState$.complete(); this.sshKeys$.complete(); this.snackBar.open('Logged out'); } }, this.errorLogger, () => console.log('loggedIn$ completed'), ); // start pulling updates this.fetch(); } private connectLiveUpdates(user: t.User): void { // handle with care let login = user.id let passcode = this.cookieService.get('sessionid'); const stompConfig: StompConfig = { // Which server? url: 'wss://'+window.location.host+'/ws', // Headers // Typical keys: login, passcode, host headers: { login: 'webpage-client:' + login, passcode: passcode, }, // How often to heartbeat? // Interval in milliseconds, set to 0 to disable heartbeat_in: 0, // Typical value 0 - disabled heartbeat_out: 20000, // Typical value 20000 - every 20 seconds // Wait in milliseconds before attempting auto reconnect // Set to 0 to disable // Typical value 15000 (15 seconds) reconnect_delay: 15000, // Will log diagnostics on console debug: false, }; this._stompService.config = stompConfig; this._stompService.initAndConnect(); let subscription = this._stompService.subscribe( '/exchange/users/' + user.id.toString() ); subscription.subscribe( (message: Message) => { let update : t.StateAPIResult = JSON.parse(message.body); if (update.error && update.error != '') { this.snackBar.open(update.error); this.messages.push(update.error); } if (update.user_state) { this.userStateUpdate(update.user_state); } }, (error: any) => { console.log(error) } ); } private userStateUpdate(newState: t.UserState): void { console.log('userStateUpdate:', newState); // did a login occur? let login = (!this.loggedIn && newState); // did a logout occur? let logout = (this.loggedIn && !newState); // -- Value updating -- if (login) { this.loggedIn = true; this.loggedIn$.next(this.loggedIn); } if (logout) { this.loggedIn = false; this.loggedIn$.next(this.loggedIn); return } if (newState) { this.userState = newState; this.userState$.next(newState); } } private stateAPIUpdate(update: t.StateAPIResult) { console.log('stateAPIUpdate:', update); if (update) { // report an occured error if (update.error) { this.errorLogger(update.error); } if (!update.user) { this.disconnect(); return } this.userStateUpdate(update.user_state); this.sar = update; this.sar$.next(update); } else { this.disconnect(); return } } private fetch(): void { this.http.get('/backend/api/state').subscribe( (data: t.StateAPIResult) => this.stateAPIUpdate(data), (err: any) => this.errorLogger(err, 'receiving data from the server') ); } // PUBLIC API public serviceDescription(service: t.Service): string { if (service.description != "") { return service.description; } return "No description"; } public getGroups(): t.Group[] { if (this.sar.user.groups) { return this.sar.user.groups.sort( function(a,b) { if (a.name < b.name) { return -1; } else if (a.name > b.name) { return 1; } else if (a.name === b.name) { return 0; } } ); } return [] } public getDeployment(service: t.Service): t.Deployment { return this.sar.user_state.deployments.find( dep => dep.service && dep.service.id === service.id ); } public getServices(group: t.Group): t.Service[] { // TODO implement // return [] } public errorLogger(error: any, msg?: string): void { console.log(error); if (msg) { this.snackBar.open('Error: '+msg); } else { this.snackBar.open('Error'); } } public login(idp: t.IdP) { this.idpService.setIdPPreference(idp); window.location.href = '/backend/auth/v1/request'; } public logout() { this.http.post('/backend/auth/v1/logout', {}).subscribe( (data: t.StateAPIResult) => this.stateAPIUpdate(data), (err: any) => { this.errorLogger(err); // force manual log out this.loggedIn = false; this.loggedIn$.next(this.loggedIn); } ); } public sentQuestionnaire(stateItemID: number, answers: Object) { return this.http.post('/backend/api/questionnaire?id='+String(stateItemID), answers).subscribe( (data: t.UserState) => { this.snackBar.open('Uploaded questionnaire'); this.userStateUpdate(data); }, (err) => { this.snackBar.open('Error uploading questionnaire'); console.log(err); this.fetch(); } ); } public deleteUser() { return this.http.delete('/backend/api/delete_user').subscribe( (data: {deleted: boolean}) => { if (data && data.deleted) { this.disconnect(); this.snackBar.open('Deleted user from server'); } }, (err) => { this.snackBar.open('Error deleting user from server'); console.log(err); this.fetch(); } ); } public uploadSshKey(key: t.NewSSHKey) { console.log('uploading key:', key); const body = { 'type': 'add', 'key': key, }; return this.http.post('/backend/api/sshkey', body).subscribe( (newKey: t.SSHKey) => { this.sshKeys.push(newKey); this.sshKeys$.next(this.sshKeys); } ); } public removeSshKey(key: t.SSHKey) { console.log('deleting key:', key); return this.http.post('/backend/api/sshkey', { 'type': 'remove', 'id': key.id, }).subscribe( (data: {deleted: boolean}) => { if (data && data.deleted) { this.sshKeys = this.sshKeys.filter( k => key.id != k.id ); this.sshKeys$.next(this.sshKeys); } } ); } public changeDeployment(action: string, group: t.Group): Observable { const body = { 'type': action, 'group': group.id, }; return this.http.post('/backend/api/deployments', body).map( (newDep: t.Deployment) => newDep, ); } // DATA SERVICE API // public subscribeSSHKeys(): Observable { return this.sshKeys$.asObservable(); } public subscribeLoggedIn(): Observable { return this.loggedIn$.asObservable(); } public subscribeState(): Observable { return this.sar$.asObservable(); } public subscribeUserState(): Observable { return this.userState$.asObservable(); } public subscribeSpecific(selector: (state: SAR) => T): Observable { return this.subscribeState().map(selector); } public subscribeServiceDeployment(service: t.Service): Observable { return this.subscribeUserState().map( (state: t.UserState) => { if (state.deployments) { return state.deployments.find( (dep: t.Deployment) => dep.service ? dep.service.id == service.id : false ); } }, ); } public subscribeGroupDeployment(group: t.Group): Observable { return this.subscribeUserState().map( (state: t.UserState) => { if (state.deployments) { return state.deployments.find( (dep: t.Deployment) => dep.group == group.id ); } }, ); } }