user.service.ts 10.7 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
      return EMPTY
Lukas Burgey's avatar
Lukas Burgey committed
222
    }
Lukas Burgey's avatar
Lukas Burgey committed
223
224
  }

Lukas Burgey's avatar
Lukas Burgey committed
225
  private logErrorAndFetch(error: any)  {
226
227
    if (error.status === 403) {
      this.login();
Lukas Burgey's avatar
Lukas Burgey committed
228
    }
229
230
231
232

    console.log(error);
    this.snackBar.open('Error');
    this.fetch();
Lukas Burgey's avatar
Lukas Burgey committed
233
    return observableThrowError(error);
Lukas Burgey's avatar
Lukas Burgey committed
234
235
  }

Lukas Burgey's avatar
Lukas Burgey committed
236
  // PUBLIC API
237
238
239
240
  public login(idp?: IdP): void {
    if (idp) {
      this.idpService.setIdPPreference(idp);
    }
241

242
    window.location.href = '/backend/auth/v1/request';
243
244
  }

Lukas Burgey's avatar
Lukas Burgey committed
245
  public logout(): void {
Lukas Burgey's avatar
Lukas Burgey committed
246
    this.http.post<State>('/backend/auth/v1/logout', undefined).subscribe(
247
      (state: State) => this.updateState(state),
Lukas Burgey's avatar
Lukas Burgey committed
248
249
250
      (err: HttpErrorResponse) => {
        this.updateState(undefined);
      }
251
252
253
    );
  }

254
  public sentQuestionnaire(stateItemID: number, answers: Object) {
255
256
257
    return this.http.post<Deployment>('/backend/api/questionnaire?id='+String(stateItemID), answers).pipe(
      catchError(this.handleError(true, "Error submitting questionnaire")),
    ).subscribe(
Lukas Burgey's avatar
Lukas Burgey committed
258
      (dep: Deployment) => this.updateDeployment(dep),
259
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
260
261
262
    );
  }

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

Lukas Burgey's avatar
Lukas Burgey committed
277
  public uploadSshKey(formData: FormData): void {
278
279
280
    this.http.post<SSHKey>('/backend/api/sshkey', formData).pipe(
      catchError(this.handleError(true, "Error changing deployment")),
    ).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
    return this.http.post('/backend/api/sshkey', {
      'type': 'remove',
      'id': key.id,
294
295
296
    }).pipe(
      catchError(this.handleError(true, "Error changing deployment")),
    ).subscribe(
Lukas Burgey's avatar
Lukas Burgey committed
297
298
299
300
301
302
303
      (data: {deleted: boolean}) => {
        if (data && data.deleted) {
          this.sshKeys = this.sshKeys.filter(
            k => key.id != k.id
          );
          this.sshKeys$.next(this.sshKeys);
        }
304
      },
305
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
306
307
    );
  }
308

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

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

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

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

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

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

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

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

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

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

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