user.service.ts 10 KB
Newer Older
Lukas Burgey's avatar
Lukas Burgey committed
1 2
import {Injectable} from '@angular/core';
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
Lukas Burgey's avatar
Lukas Burgey committed
3 4
import {MatTableDataSource} from '@angular/material';

Lukas Burgey's avatar
Lukas Burgey committed
5
import {Observable} from 'rxjs/Observable';
6
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
7
import {Subject} from 'rxjs/Subject';
Lukas Burgey's avatar
Lukas Burgey committed
8 9

import {CookieService} from 'ngx-cookie-service';
10 11
import {StompConfig, StompRService} from '@stomp/ng2-stompjs';
import {Message} from '@stomp/stompjs';
Lukas Burgey's avatar
Lukas Burgey committed
12

Lukas Burgey's avatar
Lukas Burgey committed
13
import {SnackBarService} from './snackbar.service';
14
import {IdpService} from './idp.service';
Lukas Burgey's avatar
Lukas Burgey committed
15
import * as t from './types/types.module';
16
import { StateAPIResult as SAR } from './types/types.module';
Lukas Burgey's avatar
Lukas Burgey committed
17

Lukas Burgey's avatar
Lukas Burgey committed
18 19 20

@Injectable()
export class UserService {
21 22
  private initialized = false;
  private loggedIn: boolean = false;
Lukas Burgey's avatar
Lukas Burgey committed
23
  private loggedIn$ = new BehaviorSubject<boolean>(false);
Lukas Burgey's avatar
Lukas Burgey committed
24

25 26
  private sar: SAR;
  private sar$ = <BehaviorSubject<SAR>> new BehaviorSubject(new Object);
Lukas Burgey's avatar
Lukas Burgey committed
27 28

  private sshKeys: t.SSHKey[] = new Array<t.SSHKey>();
29
  private sshKeys$ = new BehaviorSubject<t.SSHKey[]>(this.sshKeys);
Lukas Burgey's avatar
Lukas Burgey committed
30

31 32
  private userState: t.UserState;
  private userState$ = <BehaviorSubject<t.UserState>> new BehaviorSubject(new Object);
Lukas Burgey's avatar
Lukas Burgey committed
33

34
  private messages: string[] = [];
Lukas Burgey's avatar
Lukas Burgey committed
35

36 37 38
  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 : [];
Lukas Burgey's avatar
Lukas Burgey committed
39 40

  constructor(
Lukas Burgey's avatar
Lukas Burgey committed
41 42 43 44
    private cookieService: CookieService,
    private http: HttpClient,
    private snackBar: SnackBarService,
    private idpService: IdpService,
45
    private _stompService: StompRService,
Lukas Burgey's avatar
Lukas Burgey committed
46
  ) {
47
    this.connect();
48 49
  }

50 51 52 53
  // PRIVATE API
  private disconnect(): void {
    this.loggedIn = false;
    this.sshKeys$.complete();
54

55
    this.loggedIn$.next(this.loggedIn);
Lukas Burgey's avatar
Lukas Burgey committed
56 57
  }

58 59 60
  private connect(): void {
    this.subscribeState().subscribe(
      (state: SAR) => {
Lukas Burgey's avatar
Lukas Burgey committed
61
        console.log('State:', state);
62 63 64 65 66 67 68 69 70 71

        // 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);
Lukas Burgey's avatar
Lukas Burgey committed
72

73
      },
74
      this.logErrorAndFetch(),
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
      () => 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');
        }
      },
99
      this.logErrorAndFetch(),
100 101 102 103 104
      () => console.log('loggedIn$ completed'),
    );

    // start pulling updates
    this.fetch();
Lukas Burgey's avatar
Lukas Burgey committed
105 106
  }

107
  private connectLiveUpdates(user: t.User): void {
Lukas Burgey's avatar
Lukas Burgey committed
108
    // handle with care
109
    let login = user.id
Lukas Burgey's avatar
Lukas Burgey committed
110
    let passcode = this.cookieService.get('sessionid');
Lukas Burgey's avatar
Lukas Burgey committed
111

Lukas Burgey's avatar
Lukas Burgey committed
112 113
    const stompConfig: StompConfig = {
      // Which server?
114
      url: 'wss://'+window.location.host+'/ws',
115

Lukas Burgey's avatar
Lukas Burgey committed
116 117 118 119 120 121
      // Headers
      // Typical keys: login, passcode, host
      headers: {
        login: 'webpage-client:' + login,
        passcode: passcode,
      },
Lukas Burgey's avatar
Lukas Burgey committed
122

Lukas Burgey's avatar
Lukas Burgey committed
123 124 125 126 127 128
      // 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
129 130
      // Typical value 15000 (15 seconds)
      reconnect_delay: 15000,
131

Lukas Burgey's avatar
Lukas Burgey committed
132 133 134 135 136
      // Will log diagnostics on console
      debug: false,
    };
    this._stompService.config = stompConfig;
    this._stompService.initAndConnect();
Lukas Burgey's avatar
Lukas Burgey committed
137

Lukas Burgey's avatar
Lukas Burgey committed
138
    let subscription = this._stompService.subscribe(
139
      '/exchange/users/' + user.id.toString()
Lukas Burgey's avatar
Lukas Burgey committed
140
    );
141

Lukas Burgey's avatar
Lukas Burgey committed
142 143 144 145 146 147
    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);
Lukas Burgey's avatar
Lukas Burgey committed
148
        }
Lukas Burgey's avatar
Lukas Burgey committed
149
        if (update.user_state) {
Lukas Burgey's avatar
Lukas Burgey committed
150
          this.userStateUpdate(update.user_state);
Lukas Burgey's avatar
Lukas Burgey committed
151
        }
Lukas Burgey's avatar
Lukas Burgey committed
152
      },
153
      this.logErrorAndFetch(),
Lukas Burgey's avatar
Lukas Burgey committed
154
    );
Lukas Burgey's avatar
Lukas Burgey committed
155
  }
156

Lukas Burgey's avatar
Lukas Burgey committed
157 158 159
  private userStateUpdate(newState: t.UserState): void {
    console.log('userStateUpdate:', newState);

Lukas Burgey's avatar
Lukas Burgey committed
160
      // did a login occur?
161
    let login = (!this.loggedIn && newState);
Lukas Burgey's avatar
Lukas Burgey committed
162
    // did a logout occur?
163
    let logout = (this.loggedIn && !newState);
Lukas Burgey's avatar
Lukas Burgey committed
164 165 166

    // -- Value updating --
    if (login) {
167 168
      this.loggedIn = true;
      this.loggedIn$.next(this.loggedIn);
169
    }
Lukas Burgey's avatar
Lukas Burgey committed
170
    if (logout) {
171 172
      this.loggedIn = false;
      this.loggedIn$.next(this.loggedIn);
173
      return
Lukas Burgey's avatar
Lukas Burgey committed
174 175
    }

176 177 178 179
    if (newState) {
      this.userState = newState;
      this.userState$.next(newState);
    }
Lukas Burgey's avatar
wip  
Lukas Burgey committed
180 181
  }

Lukas Burgey's avatar
Lukas Burgey committed
182
  private stateAPIUpdate(update: t.StateAPIResult) {
183
    console.log('stateAPIUpdate:', update);
Lukas Burgey's avatar
Lukas Burgey committed
184

185 186 187
    if (update) {
      // report an occured error
      if (update.error) {
188
        this.logErrorAndFetch(update.error);
189
      }
Lukas Burgey's avatar
Lukas Burgey committed
190

191 192 193
      if (!update.user) {
        this.disconnect();
        return
194 195
      }

Lukas Burgey's avatar
Lukas Burgey committed
196
      this.userStateUpdate(update.user_state);
197

198 199 200 201 202 203
      this.sar = update;
      this.sar$.next(update);

    } else {
      this.disconnect();
      return
Lukas Burgey's avatar
Lukas Burgey committed
204 205 206
    }
  }

207
  private fetch(): void {
Lukas Burgey's avatar
Lukas Burgey committed
208 209
    this.http.get('/backend/api/state').subscribe(
      (data: t.StateAPIResult) => this.stateAPIUpdate(data),
210
      this.logErrorAndFetch(),
Lukas Burgey's avatar
Lukas Burgey committed
211 212 213
    );
  }

214
  // PUBLIC API
215 216 217 218 219 220
  public serviceDescription(service: t.Service): string {
    if (service.description != "") {
      return service.description;
    }
    return "No description";
  }
Lukas Burgey's avatar
Lukas Burgey committed
221

Lukas Burgey's avatar
Lukas Burgey committed
222
  public getGroups(): t.Group[] {
223 224
    if (this.sar.user.groups) {
      return this.sar.user.groups.sort(
Lukas Burgey's avatar
Lukas Burgey committed
225 226 227 228 229 230 231 232 233 234 235 236 237 238
        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 []
  }

239 240 241 242
  public getDeployment(service: t.Service): t.Deployment {
    return this.sar.user_state.deployments.find(
      dep => dep.service && dep.service.id === service.id
    );
Lukas Burgey's avatar
Lukas Burgey committed
243 244
  }

245 246 247 248
  public getServices(group: t.Group): t.Service[] {
    // TODO implement
    //
    return []
Lukas Burgey's avatar
Lukas Burgey committed
249 250
  }

251
  public logErrorAndFetch(msg?: string): ((any) => void) {
Lukas Burgey's avatar
Lukas Burgey committed
252
    if (msg) {
253 254 255 256 257
      return (error: any) => {
        console.log(error);
        this.snackBar.open('Error: '+msg);
        this.fetch();
      };
Lukas Burgey's avatar
Lukas Burgey committed
258
    } else {
259 260 261 262 263
      return (error: any) => {
        console.log(error);
        this.snackBar.open('Error');
        this.fetch();
      };
Lukas Burgey's avatar
Lukas Burgey committed
264
    }
Lukas Burgey's avatar
Lukas Burgey committed
265 266 267
  }

  public login(idp: t.IdP) {
268
    this.idpService.setIdPPreference(idp);
269

270
    window.location.href = '/backend/auth/v1/request';
271 272
  }

Lukas Burgey's avatar
Lukas Burgey committed
273
  public logout() {
274
    this.http.post('/backend/auth/v1/logout', {}).subscribe(
275
      (data: t.StateAPIResult) => this.stateAPIUpdate(data),
276
      this.logErrorAndFetch(),
277 278 279
    );
  }

280
  public sentQuestionnaire(stateItemID: number, answers: Object) {
281
    return this.http.post('/backend/api/questionnaire?id='+String(stateItemID), answers).subscribe(
Lukas Burgey's avatar
Lukas Burgey committed
282
      (data: t.UserState) => {
Lukas Burgey's avatar
Lukas Burgey committed
283
        this.snackBar.open('Uploaded questionnaire');
Lukas Burgey's avatar
Lukas Burgey committed
284
        this.userStateUpdate(data);
Lukas Burgey's avatar
Lukas Burgey committed
285
      },
286
      this.logErrorAndFetch(),
Lukas Burgey's avatar
Lukas Burgey committed
287 288 289
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
290
  public deleteUser() {
Lukas Burgey's avatar
Lukas Burgey committed
291 292 293
    return this.http.delete('/backend/api/delete_user').subscribe(
      (data: {deleted: boolean}) => {
        if (data && data.deleted) {
294
          this.disconnect();
Lukas Burgey's avatar
Lukas Burgey committed
295 296
          this.snackBar.open('Deleted user from server');
        }
Lukas Burgey's avatar
Lukas Burgey committed
297
      },
298
      this.logErrorAndFetch('Error deleting user from server'),
Lukas Burgey's avatar
Lukas Burgey committed
299 300
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
301 302 303 304 305 306 307 308 309 310 311

  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);
312 313
      },
      this.logErrorAndFetch(),
Lukas Burgey's avatar
Lukas Burgey committed
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
    );
  }

  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);
        }
330 331
      },
      this.logErrorAndFetch(),
Lukas Burgey's avatar
Lukas Burgey committed
332 333
    );
  }
334

335

336 337 338 339 340 341 342
  public changeDeployment(action: string, group: t.Group): Observable<t.Deployment> {
    const body = {
      'type': action,
      'group': group.id,
    };
    return this.http.post('/backend/api/deployments', body).map(
      (newDep: t.Deployment) => newDep,
343
      this.logErrorAndFetch(),
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
    );
  }

  // DATA SERVICE API
  //
  public subscribeSSHKeys(): Observable<t.SSHKey[]> {
    return this.sshKeys$.asObservable();
  }

  public subscribeLoggedIn(): Observable<boolean> {
    return this.loggedIn$.asObservable();
  }

  public subscribeState(): Observable<SAR> {
    return this.sar$.asObservable();
  }

  public subscribeUserState(): Observable<t.UserState> {
    return this.userState$.asObservable();
  }

  public subscribeSpecific<T>(selector: (state: SAR) => T): Observable<T> {
    return this.subscribeState().map(selector);
  }

  public subscribeServiceDeployment(service: t.Service): Observable<t.Deployment> {
    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<t.Deployment> {
    return this.subscribeUserState().map(
      (state: t.UserState) => {
        if (state.deployments) {
          return state.deployments.find(
            (dep: t.Deployment) => dep.group == group.id
          );
        }
      },
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
392
}