user.service.ts 11.5 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
import { throwError as observableThrowError,  Observable, BehaviorSubject, of, EMPTY } from 'rxjs';
Lukas Burgey's avatar
Lukas Burgey committed
4
import { map, catchError, combineLatest } from 'rxjs/operators';
Lukas Burgey's avatar
Lukas Burgey committed
5

Lukas Burgey's avatar
Lukas Burgey committed
6
7
8
9
10
11
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';
Lukas Burgey's avatar
Lukas Burgey committed
12
import { PreferencesService, Prefs } from './preferences/preferences.service';
Lukas Burgey's avatar
Lukas Burgey committed
13

Lukas Burgey's avatar
Lukas Burgey committed
14
import {
Lukas Burgey's avatar
Lukas Burgey committed
15
  VO, User, Update, State, Deployment, DeploymentState, SSHKey, NewSSHKey, IdP, Service, Site, JSONObject
Lukas Burgey's avatar
Lukas Burgey committed
16
} 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
  private initialized = false;
Lukas Burgey's avatar
Lukas Burgey committed
22
23
24
25
26
  private loggedIn = false;

  // relogin on failed XHR calls
  // is turned off when the user is deactivated
  private autoReLogin = true;
Lukas Burgey's avatar
Lukas Burgey committed
27

28
  private user: User;
Lukas Burgey's avatar
Lukas Burgey committed
29
  private user$: BehaviorSubject<User>;
Lukas Burgey's avatar
Lukas Burgey committed
30

Lukas Burgey's avatar
Lukas Burgey committed
31
  private sshKeys: Map<number, SSHKey> = new Map([]);
Lukas Burgey's avatar
Lukas Burgey committed
32
  private sshKeys$ = new BehaviorSubject<SSHKey[]>([]);
Lukas Burgey's avatar
Lukas Burgey committed
33

34
35
36
  private deploymentStates: Map<number, DeploymentState> = new Map([]);
  private deploymentStates$ = new BehaviorSubject<DeploymentState[]>([]);

37
  private deployments: Map<number, Deployment> = new Map([]);
Lukas Burgey's avatar
Lukas Burgey committed
38
  private deployments$ = new BehaviorSubject<Deployment[]>([]);
Lukas Burgey's avatar
Lukas Burgey committed
39

40
  public voSelector = (user: User) => user ? user.vos : [];
Lukas Burgey's avatar
Lukas Burgey committed
41
  public serviceSelector = (user: User) => user ? user.services : [];
Lukas Burgey's avatar
Lukas Burgey committed
42
43

  constructor(
Lukas Burgey's avatar
Lukas Burgey committed
44
45
46
47
    private cookieService: CookieService,
    private http: HttpClient,
    private snackBar: SnackBarService,
    private idpService: IdpService,
Lukas Burgey's avatar
Lukas Burgey committed
48
    private stompService: StompRService,
Lukas Burgey's avatar
Lukas Burgey committed
49
    private prefs: PreferencesService,
Lukas Burgey's avatar
Lukas Burgey committed
50
  ) {
Lukas Burgey's avatar
Lukas Burgey committed
51
    this.user$ = new BehaviorSubject(null);
52
    this.connect();
53
54
  }

55
56
  // PRIVATE API
  private connect(): void {
Lukas Burgey's avatar
Lukas Burgey committed
57
58
    this.fetch();

Lukas Burgey's avatar
Lukas Burgey committed
59
    this.userSrc().subscribe(
60
      (newUser: User) => {
61
        console.log('user$:', newUser);
Lukas Burgey's avatar
Lukas Burgey committed
62
63
64
65
66
67
68
69
70
71
72
73

        if (newUser == undefined || newUser == null) {
          // LOGGED OUT
          // show the logout to the user
          if (this.loggedIn) {
            this.snackBar.open('Logged out');

            // purge all values
            this.stompService.disconnect();
            this.user = undefined;
            this.deployments = new Map([]);
            this.deployments$.next([]);
Lukas Burgey's avatar
Lukas Burgey committed
74
            this.sshKeys = new Map([]);
Lukas Burgey's avatar
Lukas Burgey committed
75
76
77
78
79
80
81
82
83
84
85
            this.sshKeys$.next([]);
          }

          this.loggedIn =  false;
        } else {
          // LOGGED IN
          // show the login to the user
          if (!this.loggedIn) {
            this.snackBar.open('Logged in');
          }

86
87
88
89
          if (newUser.id) {
            this.connectLiveUpdates(newUser.id);
          }
          if (newUser.ssh_keys) {
Lukas Burgey's avatar
Lukas Burgey committed
90
91
92
93
94
            this.sshKeys = new Map([]);
            newUser.ssh_keys.forEach(
              (key: SSHKey) => this.sshKeys.set(key.id, key),
            );
            this.sshKeys$.next(Array.from(this.sshKeys.values()));
95
96
97
98
99
100
101
102
103
104
          }
          if (newUser.deployments) {
            newUser.deployments.forEach(
              (newDep: Deployment) => {
                this.deployments.set(newDep.id,  newDep);
              }
            );
            this.deployments$.next(Array.from(this.deployments.values()));
          }

105
106
107
108
109
110
111
          if (newUser.states) {
            newUser.states.forEach((state: DeploymentState) => {
              this.deploymentStates.set(state.id, state)
            });
            this.deploymentStates$.next(Array.from(this.deploymentStates.values()));
          }

Lukas Burgey's avatar
Lukas Burgey committed
112
          this.loggedIn = true;
113
114
115
          this.initialized = true;
        }
      },
Lukas Burgey's avatar
Lukas Burgey committed
116
117
      this.handleError(true),
      () => console.log('user$ is complete'),
118
    );
Lukas Burgey's avatar
Lukas Burgey committed
119
120
  }

121
  private connectLiveUpdates(userID: number): void {
Lukas Burgey's avatar
Lukas Burgey committed
122
    // handle with care
123
124
    const login = userID
    const passcode = this.cookieService.get('sessionid');
Lukas Burgey's avatar
Lukas Burgey committed
125
126
    const stompConfig: StompConfig = {
      // Which server?
127
      url: 'wss://'+window.location.host+'/ws',
128

Lukas Burgey's avatar
Lukas Burgey committed
129
130
131
132
133
134
      // Headers
      // Typical keys: login, passcode, host
      headers: {
        login: 'webpage-client:' + login,
        passcode: passcode,
      },
Lukas Burgey's avatar
Lukas Burgey committed
135

Lukas Burgey's avatar
Lukas Burgey committed
136
137
138
139
140
141
      // 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
142
143
      // Typical value 15000 (15 seconds)
      reconnect_delay: 15000,
144

Lukas Burgey's avatar
Lukas Burgey committed
145
146
147
      // Will log diagnostics on console
      debug: false,
    };
148

Lukas Burgey's avatar
Lukas Burgey committed
149
150
    this.stompService.config = stompConfig;
    this.stompService.initAndConnect();
Lukas Burgey's avatar
Lukas Burgey committed
151

152
    const subscription = this.stompService.subscribe(
153
      '/exchange/users/' + userID.toString()
Lukas Burgey's avatar
Lukas Burgey committed
154
    );
155

Lukas Burgey's avatar
Lukas Burgey committed
156
157
    subscription.subscribe(
      (message: Message) => {
158
159
160
        let update: Update = JSON.parse(message.body);
        console.log('update:', update);

Lukas Burgey's avatar
Lukas Burgey committed
161
        // TODO rename error to msg
Lukas Burgey's avatar
Lukas Burgey committed
162
163
        if (update.error && update.error != '') {
          this.snackBar.open(update.error);
Lukas Burgey's avatar
Lukas Burgey committed
164
        }
165

166
167
168
169
        if (update.deployment_state) {
          this.updateDeploymentState(update.deployment_state);
        }

170
171
        if (update.deployment) {
          this.updateDeployment(update.deployment);
Lukas Burgey's avatar
Lukas Burgey committed
172
        }
Lukas Burgey's avatar
Lukas Burgey committed
173
      },
174
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
175
    );
Lukas Burgey's avatar
Lukas Burgey committed
176
  }
177

Lukas Burgey's avatar
Lukas Burgey committed
178
  private updateDeployment(dep: Deployment): void {
179
180
181
    if (dep != undefined && dep != null) {
      dep.states.forEach((state: DeploymentState) => this.updateDeploymentState(state),
      );
Lukas Burgey's avatar
Lukas Burgey committed
182
183
184
185
      this.deployments.set(dep.id, dep);
      this.deployments$.next(Array.from(this.deployments.values()));
    }
  }
Lukas Burgey's avatar
Lukas Burgey committed
186

187
188
189
190
191
192
193
  private updateDeploymentState(ds: DeploymentState): void {
    if (ds != undefined && ds != null) {
      this.deploymentStates.set(ds.id, ds);
      this.deploymentStates$.next(Array.from(this.deploymentStates.values()));
    }
  }

Lukas Burgey's avatar
Lukas Burgey committed
194
  private updateState(update: State): void {
195
196
    if (update) {
      // report an occured error
Lukas Burgey's avatar
Lukas Burgey committed
197
198
199
200
201
      if (update.msg) {
        this.snackBar.open(update.msg);
      }
      if (update.session && update.session.deactivated) {
        this.autoReLogin = false;
202
      }
Lukas Burgey's avatar
Lukas Burgey committed
203

Lukas Burgey's avatar
Lukas Burgey committed
204
205
      console.log(update.session);

Lukas Burgey's avatar
Lukas Burgey committed
206
207
      this.user = update.user;
      this.user$.next(this.user);
208
209

    } else {
Lukas Burgey's avatar
Lukas Burgey committed
210
      this.user$.next(undefined);
Lukas Burgey's avatar
Lukas Burgey committed
211
212
213
    }
  }

214
  private fetch(): void {
Lukas Burgey's avatar
Lukas Burgey committed
215
    this.http.get<State>('/backend/api/state').subscribe(
216
      (state: State) => this.updateState(state),
Lukas Burgey's avatar
Lukas Burgey committed
217
      this.handleError(false, 'Error fetching state. Try again later'),
Lukas Burgey's avatar
Lukas Burgey committed
218
219
220
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
221
222
  private handleError(fetch: boolean, msg?: string) {
    return (error: any) => {
Lukas Burgey's avatar
Lukas Burgey committed
223
      if (error.status === 403 && this.autoReLogin) {
Lukas Burgey's avatar
Lukas Burgey committed
224
225
226
        this.login();
        return
      }
227

Lukas Burgey's avatar
Lukas Burgey committed
228
229
230
231
232
233
234
      if (msg) {
        this.snackBar.open(msg);
      }
      console.log('fetch:', error);
      if (fetch) {
        this.fetch();
      }
235
      return EMPTY
Lukas Burgey's avatar
Lukas Burgey committed
236
    }
Lukas Burgey's avatar
Lukas Burgey committed
237
238
  }

Lukas Burgey's avatar
Lukas Burgey committed
239
  private logErrorAndFetch(error: any)  {
Lukas Burgey's avatar
Lukas Burgey committed
240
    if (error.status === 403 && this.autoReLogin) {
241
      this.login();
Lukas Burgey's avatar
Lukas Burgey committed
242
    }
243
244
245
246

    console.log(error);
    this.snackBar.open('Error');
    this.fetch();
Lukas Burgey's avatar
Lukas Burgey committed
247
    return observableThrowError(error);
Lukas Burgey's avatar
Lukas Burgey committed
248
249
  }

Lukas Burgey's avatar
Lukas Burgey committed
250
  // PUBLIC API
251
252
253
254
  public login(idp?: IdP): void {
    if (idp) {
      this.idpService.setIdPPreference(idp);
    }
255

256
    window.location.href = '/backend/auth/v1/request';
257
258
  }

Lukas Burgey's avatar
Lukas Burgey committed
259
  public logout(): void {
Lukas Burgey's avatar
Lukas Burgey committed
260
    this.http.post<State>('/backend/auth/v1/logout', undefined).subscribe(
261
      (state: State) => this.updateState(state),
Lukas Burgey's avatar
Lukas Burgey committed
262
263
264
      (err: HttpErrorResponse) => {
        this.updateState(undefined);
      }
265
266
267
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
268
  public sentQuestionnaire(stateItemID: number, answers: JSONObject) {
Lukas Burgey's avatar
Lukas Burgey committed
269
270
271
    return this.http.patch<DeploymentState>(
      `/rest/dep-state?id=${ stateItemID }`,
      {
Lukas Burgey's avatar
Lukas Burgey committed
272
        'answers': answers,
Lukas Burgey's avatar
Lukas Burgey committed
273
274
      },
  ).pipe(
Lukas Burgey's avatar
Lukas Burgey committed
275
      catchError(this.handleError(true, "Error submitting answers")),
276
    ).subscribe(
Lukas Burgey's avatar
Lukas Burgey committed
277
      (state: DeploymentState) => this.updateDeploymentState(state),
278
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
279
280
281
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
282
  public deleteUser() {
283
    return this.http.delete('/rest/user').pipe(
284
285
      catchError(this.handleError(true, "Error deleting user")),
    ).subscribe(
286
      _ => {
Lukas Burgey's avatar
Lukas Burgey committed
287
          this.user$.next(undefined);
Lukas Burgey's avatar
Lukas Burgey committed
288
          this.snackBar.open('Deleted user from server');
Lukas Burgey's avatar
Lukas Burgey committed
289
      },
290
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
291
292
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
293

294
295
  public uploadSshKey(name: string, key: string | ArrayBuffer): void {
    this.http.post<SSHKey>('/rest/ssh-keys', {'name': name, 'key': key}).pipe(
296
297
      catchError(this.handleError(true, "Error changing deployment")),
    ).subscribe(
298
      (newKey: SSHKey) => {
Lukas Burgey's avatar
Lukas Burgey committed
299
300
        this.sshKeys.set(newKey.id, newKey);
        this.sshKeys$.next(Array.from(this.sshKeys.values()));
301
      },
302
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
303
304
305
    );
  }

306
  public removeSshKey(key: SSHKey) {
Lukas Burgey's avatar
Lukas Burgey committed
307
    console.log('Deleting key:', key);
308
    return this.http.delete('/rest/ssh-key?id='+key.id.toString()).pipe(
309
310
      catchError(this.handleError(true, "Error changing deployment")),
    ).subscribe(
311
      _ => {
Lukas Burgey's avatar
Lukas Burgey committed
312
313
        this.sshKeys.delete(key.id);
        this.sshKeys$.next(Array.from(this.sshKeys.values()));
314
      },
315
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
316
317
    );
  }
318

Lukas Burgey's avatar
Lukas Burgey committed
319
  public changeVODeployment(action: string, vo: VO): void {
320
321
    const body = {
      'type': action,
322
      'vo': vo.id,
323
    };
324
    this.http.post<Deployment>('/backend/api/deployments', body).pipe(
325
      catchError(this.handleError(true, "Error changing deployment")),
326
327
    ).subscribe(
      (dep: Deployment) => this.updateDeployment(dep),
328
329
330
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
331
332
333
334
335
336
  public changeServiceDeployment(action: string, service: Service): void {
    const body = {
      'type': action,
      'service': service.id,
    };
    this.http.post<Deployment>('/backend/api/deployments', body).pipe(
337
      catchError(this.handleError(true, "Error changing deployment")),
Lukas Burgey's avatar
Lukas Burgey committed
338
339
340
341
342
    ).subscribe(
      (dep: Deployment) => this.updateDeployment(dep),
    );
  }

343
344
  // DATA SERVICE API
  //
Lukas Burgey's avatar
Lukas Burgey committed
345
  public subscribeSpecific<T>(selector: (user: User) => T): Observable<T> {
Lukas Burgey's avatar
Lukas Burgey committed
346
    return this.userSrc().pipe(map(selector));
347
348
  }

Lukas Burgey's avatar
Lukas Burgey committed
349
  public sshKeysSrc(): Observable<SSHKey[]> {
Lukas Burgey's avatar
Lukas Burgey committed
350
    return this.sshKeys$.asObservable();
351
352
  }

Lukas Burgey's avatar
Lukas Burgey committed
353
  public userSrc(): Observable<User> {
354
    return this.user$.asObservable();
355
356
  }

Lukas Burgey's avatar
Lukas Burgey committed
357
  public deploymentsSrc(): Observable<Deployment[]> {
358
    return this.deployments$.asObservable();
359
360
  }

Lukas Burgey's avatar
Lukas Burgey committed
361
  public depStatesSrc(): Observable<DeploymentState[]> {
362
363
364
    return this.deploymentStates$.asObservable();
  }

Lukas Burgey's avatar
Lukas Burgey committed
365
  public subscribeStateFor(service: Service): Observable<DeploymentState> {
366
367
    return this.deploymentStates$.asObservable().pipe(
      map((states: DeploymentState[]) => states.find(
Lukas Burgey's avatar
Lukas Burgey committed
368
          (dsi: DeploymentState) => dsi.service.id == service.id,
369
370
371
372
      )),
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
373
  public subscribeDeployment(selector: (dep: Deployment) => boolean): Observable<Deployment> {
Lukas Burgey's avatar
Lukas Burgey committed
374
    return this.deploymentsSrc().pipe(
Lukas Burgey's avatar
Lukas Burgey committed
375
      map((deployments: Deployment[]) => {
376
          return deployments.find(
Lukas Burgey's avatar
Lukas Burgey committed
377
            (dep: Deployment) => selector(dep),
378
379
          );
        }
380
      ),
381
382
383
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
384
  public servicesSrc(): Observable<Service[]> {
Lukas Burgey's avatar
Lukas Burgey committed
385
    return this.subscribeSpecific(this.serviceSelector);
386
  }
Lukas Burgey's avatar
Lukas Burgey committed
387

Lukas Burgey's avatar
Lukas Burgey committed
388
  public vosSrc(): Observable<VO[]> {
Lukas Burgey's avatar
Lukas Burgey committed
389
390
    return this.prefs.connect().pipe(
      combineLatest(
Lukas Burgey's avatar
Lukas Burgey committed
391
392
393
394
395
        this.user$.asObservable(),
        (prefs: Prefs, user: User) => {
          const vos = this.voSelector(user);
          const services = this.serviceSelector(user);

Lukas Burgey's avatar
Lukas Burgey committed
396
          if (prefs.showEmptyVOs) {
Lukas Burgey's avatar
Lukas Burgey committed
397
            // filter out VOs that have no services
Lukas Burgey's avatar
Lukas Burgey committed
398
            return vos.filter(
Lukas Burgey's avatar
Lukas Burgey committed
399
              (vo: VO) => services.some(
Lukas Burgey's avatar
Lukas Burgey committed
400
401
402
403
404
405
                (s: Service) => s.vos.some(
                  svo => svo.id === vo.id,
                ),
              ),
            );
          }
Lukas Burgey's avatar
Lukas Burgey committed
406

Lukas Burgey's avatar
Lukas Burgey committed
407
408
409
410
411
          return vos;
        }
      )
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
412
}