user.service.ts 10.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';
4
import { map, catchError } 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

Lukas Burgey's avatar
Lukas Burgey committed
13
import {
14
  VO, User, Update, State, Deployment, DeploymentState, SSHKey, NewSSHKey, IdP, Service, Site
Lukas Burgey's avatar
Lukas Burgey committed
15
} from './types/types.module';
Lukas Burgey's avatar
Lukas Burgey committed
16

Lukas Burgey's avatar
Lukas Burgey committed
17
18
19

@Injectable()
export class UserService {
20
21
  private initialized = false;
  private loggedIn: boolean = false;
Lukas Burgey's avatar
Lukas Burgey committed
22

23
  private user: User;
Lukas Burgey's avatar
Lukas Burgey committed
24
  private user$: BehaviorSubject<User>;
Lukas Burgey's avatar
Lukas Burgey committed
25

26
  private sshKeys: SSHKey[] = new Array<SSHKey>();
Lukas Burgey's avatar
Lukas Burgey committed
27
  private sshKeys$ = new BehaviorSubject<SSHKey[]>([]);
Lukas Burgey's avatar
Lukas Burgey committed
28

29
30
31
  private deploymentStates: Map<number, DeploymentState> = new Map([]);
  private deploymentStates$ = new BehaviorSubject<DeploymentState[]>([]);

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

35
  public voSelector = (user: User) => user ? user.vos : [];
36
  public serviceSelector = (user: User) => user ? user.services ? user.services : [] : [];
Lukas Burgey's avatar
Lukas Burgey committed
37
38

  constructor(
Lukas Burgey's avatar
Lukas Burgey committed
39
40
41
42
    private cookieService: CookieService,
    private http: HttpClient,
    private snackBar: SnackBarService,
    private idpService: IdpService,
Lukas Burgey's avatar
Lukas Burgey committed
43
    private stompService: StompRService,
Lukas Burgey's avatar
Lukas Burgey committed
44
  ) {
Lukas Burgey's avatar
Lukas Burgey committed
45
    this.user$ = new BehaviorSubject(null);
46
    this.connect();
47
48
  }

49
50
  // PRIVATE API
  private connect(): void {
Lukas Burgey's avatar
Lukas Burgey committed
51
52
    this.fetch();

53
54
    this.subscribeUser().subscribe(
      (newUser: User) => {
55
        console.log('user$:', newUser);
Lukas Burgey's avatar
Lukas Burgey committed
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

        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([]);
            this.sshKeys = [];
            this.sshKeys$.next([]);
          }


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

81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
          if (newUser.id) {
            this.connectLiveUpdates(newUser.id);
          }
          if (newUser.ssh_keys) {
            this.sshKeys = newUser.ssh_keys;
            this.sshKeys$.next(this.sshKeys);
          }
          if (newUser.deployments) {
            newUser.deployments.forEach(
              (newDep: Deployment) => {
                this.deployments.set(newDep.id,  newDep);
              }
            );
            this.deployments$.next(Array.from(this.deployments.values()));
          }

97
98
99
100
101
102
103
          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
104
          this.loggedIn = true;
105
106
107
          this.initialized = true;
        }
      },
Lukas Burgey's avatar
Lukas Burgey committed
108
109
      this.handleError(true),
      () => console.log('user$ is complete'),
110
    );
Lukas Burgey's avatar
Lukas Burgey committed
111
112
  }

113
  private connectLiveUpdates(userID: number): void {
Lukas Burgey's avatar
Lukas Burgey committed
114
    // handle with care
115
116
    const login = userID
    const passcode = this.cookieService.get('sessionid');
Lukas Burgey's avatar
Lukas Burgey committed
117
118
    const stompConfig: StompConfig = {
      // Which server?
119
      url: 'wss://'+window.location.host+'/ws',
120

Lukas Burgey's avatar
Lukas Burgey committed
121
122
123
124
125
126
      // Headers
      // Typical keys: login, passcode, host
      headers: {
        login: 'webpage-client:' + login,
        passcode: passcode,
      },
Lukas Burgey's avatar
Lukas Burgey committed
127

Lukas Burgey's avatar
Lukas Burgey committed
128
129
130
131
132
133
      // 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
134
135
      // Typical value 15000 (15 seconds)
      reconnect_delay: 15000,
136

Lukas Burgey's avatar
Lukas Burgey committed
137
138
139
      // Will log diagnostics on console
      debug: false,
    };
140

Lukas Burgey's avatar
Lukas Burgey committed
141
142
    this.stompService.config = stompConfig;
    this.stompService.initAndConnect();
Lukas Burgey's avatar
Lukas Burgey committed
143

144
    const subscription = this.stompService.subscribe(
145
      '/exchange/users/' + userID.toString()
Lukas Burgey's avatar
Lukas Burgey committed
146
    );
147

Lukas Burgey's avatar
Lukas Burgey committed
148
149
    subscription.subscribe(
      (message: Message) => {
150
151
152
        let update: Update = JSON.parse(message.body);
        console.log('update:', update);

Lukas Burgey's avatar
Lukas Burgey committed
153
154
        if (update.error && update.error != '') {
          this.snackBar.open(update.error);
Lukas Burgey's avatar
Lukas Burgey committed
155
        }
156

157
158
159
160
        if (update.deployment_state) {
          this.updateDeploymentState(update.deployment_state);
        }

161
162
        if (update.deployment) {
          this.updateDeployment(update.deployment);
Lukas Burgey's avatar
Lukas Burgey committed
163
        }
Lukas Burgey's avatar
Lukas Burgey committed
164
      },
165
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
166
    );
Lukas Burgey's avatar
Lukas Burgey committed
167
  }
168

Lukas Burgey's avatar
Lukas Burgey committed
169
  private updateDeployment(dep: Deployment): void {
170
171
172
    if (dep != undefined && dep != null) {
      dep.states.forEach((state: DeploymentState) => this.updateDeploymentState(state),
      );
Lukas Burgey's avatar
Lukas Burgey committed
173
174
175
176
      this.deployments.set(dep.id, dep);
      this.deployments$.next(Array.from(this.deployments.values()));
    }
  }
Lukas Burgey's avatar
Lukas Burgey committed
177

178
179
180
181
182
183
184
  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
185
  private updateState(update: State): void {
186
187
188
    if (update) {
      // report an occured error
      if (update.error) {
189
        this.snackBar.open(update.error);
190
      }
Lukas Burgey's avatar
Lukas Burgey committed
191

Lukas Burgey's avatar
Lukas Burgey committed
192
193
      this.user = update.user;
      this.user$.next(this.user);
194
195

    } else {
Lukas Burgey's avatar
Lukas Burgey committed
196
      this.user$.next(undefined);
Lukas Burgey's avatar
Lukas Burgey committed
197
198
199
    }
  }

200
  private fetch(): void {
Lukas Burgey's avatar
Lukas Burgey committed
201
    this.http.get<State>('/backend/api/state').subscribe(
202
      (state: State) => this.updateState(state),
Lukas Burgey's avatar
Lukas Burgey committed
203
      this.handleError(false, 'Error fetching state. Try again later'),
Lukas Burgey's avatar
Lukas Burgey committed
204
205
206
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
207
208
209
210
211
212
  private handleError(fetch: boolean, msg?: string) {
    return (error: any) => {
      if (error.status == 403) {
        this.login();
        return
      }
213

Lukas Burgey's avatar
Lukas Burgey committed
214
215
216
217
218
219
220
      if (msg) {
        this.snackBar.open(msg);
      }
      console.log('fetch:', error);
      if (fetch) {
        this.fetch();
      }
221
222
    }
  }
Lukas Burgey's avatar
Lukas Burgey committed
223

Lukas Burgey's avatar
Lukas Burgey committed
224
225
226
  private catchError(fetch: boolean, msg?: string) {
    return (error: any) => {
      this.handleError(fetch, msg)(error);
227
      return EMPTY;
Lukas Burgey's avatar
Lukas Burgey committed
228
    }
Lukas Burgey's avatar
Lukas Burgey committed
229
230
  }

Lukas Burgey's avatar
Lukas Burgey committed
231
  private logErrorAndFetch(error: any)  {
232
233
    if (error.status === 403) {
      this.login();
Lukas Burgey's avatar
Lukas Burgey committed
234
    }
235
236
237
238

    console.log(error);
    this.snackBar.open('Error');
    this.fetch();
Lukas Burgey's avatar
Lukas Burgey committed
239
    return observableThrowError(error);
Lukas Burgey's avatar
Lukas Burgey committed
240
241
  }

Lukas Burgey's avatar
Lukas Burgey committed
242
  // PUBLIC API
243
244
245
246
  public login(idp?: IdP): void {
    if (idp) {
      this.idpService.setIdPPreference(idp);
    }
247

248
    window.location.href = '/backend/auth/v1/request';
249
250
  }

Lukas Burgey's avatar
Lukas Burgey committed
251
  public logout(): void {
Lukas Burgey's avatar
Lukas Burgey committed
252
    this.http.post<State>('/backend/auth/v1/logout', undefined).subscribe(
253
      (state: State) => this.updateState(state),
Lukas Burgey's avatar
Lukas Burgey committed
254
255
256
      (err: HttpErrorResponse) => {
        this.updateState(undefined);
      }
257
258
259
    );
  }

260
  public sentQuestionnaire(stateItemID: number, answers: Object) {
Lukas Burgey's avatar
Lukas Burgey committed
261
262
    return this.http.post<Deployment>('/backend/api/questionnaire?id='+String(stateItemID), answers).subscribe(
      (dep: Deployment) => this.updateDeployment(dep),
263
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
264
265
266
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
267
  public deleteUser() {
Lukas Burgey's avatar
Lukas Burgey committed
268
269
270
    return this.http.delete('/backend/api/delete_user').subscribe(
      (data: {deleted: boolean}) => {
        if (data && data.deleted) {
Lukas Burgey's avatar
Lukas Burgey committed
271
          this.user$.next(undefined);
Lukas Burgey's avatar
Lukas Burgey committed
272
273
          this.snackBar.open('Deleted user from server');
        }
Lukas Burgey's avatar
Lukas Burgey committed
274
      },
275
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
276
277
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
278

Lukas Burgey's avatar
Lukas Burgey committed
279
280
  public uploadSshKey(formData: FormData): void {
    this.http.post<SSHKey>('/backend/api/sshkey', formData).subscribe(
281
      (newKey: SSHKey) => {
Lukas Burgey's avatar
Lukas Burgey committed
282
283
        this.sshKeys.push(newKey);
        this.sshKeys$.next(this.sshKeys);
284
      },
285
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
286
287
288
    );
  }

289
  public removeSshKey(key: SSHKey) {
Lukas Burgey's avatar
Lukas Burgey committed
290
    console.log('Deleting key:', key);
Lukas Burgey's avatar
Lukas Burgey committed
291
292
293
294
295
296
297
298
299
300
301
    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);
        }
302
      },
303
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
304
305
    );
  }
306

Lukas Burgey's avatar
Lukas Burgey committed
307
  public changeVODeployment(action: string, vo: VO): void {
308
309
    const body = {
      'type': action,
310
      'vo': vo.id,
311
    };
312
    this.http.post<Deployment>('/backend/api/deployments', body).pipe(
313
      catchError(this.catchError(true, "Error changing deployment")),
314
315
    ).subscribe(
      (dep: Deployment) => this.updateDeployment(dep),
316
317
318
    );
  }

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

331
332
  // DATA SERVICE API
  //
Lukas Burgey's avatar
Lukas Burgey committed
333
  public subscribeSpecific<T>(selector: (user: User) => T): Observable<T> {
334
    return this.subscribeUser().pipe(map(selector));
335
336
  }

Lukas Burgey's avatar
Lukas Burgey committed
337
338
  public subscribeSSHKeys(): Observable<SSHKey[]> {
    return this.sshKeys$.asObservable();
339
340
  }

341
342
  public subscribeUser(): Observable<User> {
    return this.user$.asObservable();
343
344
  }

345
346
  public subscribeDeployments(): Observable<Deployment[]> {
    return this.deployments$.asObservable();
347
348
  }

349
350
351
352
  public subscribeDeploymentStates(): Observable<DeploymentState[]> {
    return this.deploymentStates$.asObservable();
  }

Lukas Burgey's avatar
Lukas Burgey committed
353
  public subscribeStateFor(service: Service): Observable<DeploymentState> {
354
355
    return this.deploymentStates$.asObservable().pipe(
      map((states: DeploymentState[]) => states.find(
Lukas Burgey's avatar
Lukas Burgey committed
356
          (dsi: DeploymentState) => dsi.service.id == service.id,
357
358
359
360
      )),
    );
  }

361
362
363
  public subscribeVOs(): Observable<VO[]> {
    let voSelector = (user: User) => user ? user.vos : [];
    return this.subscribeSpecific<VO[]>(voSelector);
364
365
  }

Lukas Burgey's avatar
Lukas Burgey committed
366
  public subscribeDeployment(selector: (dep: Deployment) => boolean): Observable<Deployment> {
367
    return this.subscribeDeployments().pipe(
Lukas Burgey's avatar
Lukas Burgey committed
368
      map((deployments: Deployment[]) => {
369
          return deployments.find(
Lukas Burgey's avatar
Lukas Burgey committed
370
            (dep: Deployment) => selector(dep),
371
372
          );
        }
373
      ),
374
375
376
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
377
378
  public subscribeServices(): Observable<Service[]> {
    return this.subscribeSpecific(this.serviceSelector);
379
  }
Lukas Burgey's avatar
Lukas Burgey committed
380
}