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
243
244
245
246
247
248
249
  // PUBLIC API
  public serviceDescription(service: Service): string {
    if (service.description != "") {
      return service.description;
    }
    return "No description";
  }

250
251
252
253
  public login(idp?: IdP): void {
    if (idp) {
      this.idpService.setIdPPreference(idp);
    }
254

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

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

267
  public sentQuestionnaire(stateItemID: number, answers: Object) {
Lukas Burgey's avatar
Lukas Burgey committed
268
269
    return this.http.post<Deployment>('/backend/api/questionnaire?id='+String(stateItemID), answers).subscribe(
      (dep: Deployment) => this.updateDeployment(dep),
270
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
271
272
273
    );
  }

Lukas Burgey's avatar
Lukas Burgey committed
274
  public deleteUser() {
Lukas Burgey's avatar
Lukas Burgey committed
275
276
277
    return this.http.delete('/backend/api/delete_user').subscribe(
      (data: {deleted: boolean}) => {
        if (data && data.deleted) {
Lukas Burgey's avatar
Lukas Burgey committed
278
          this.user$.next(undefined);
Lukas Burgey's avatar
Lukas Burgey committed
279
280
          this.snackBar.open('Deleted user from server');
        }
Lukas Burgey's avatar
Lukas Burgey committed
281
      },
282
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
283
284
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
285

Lukas Burgey's avatar
Lukas Burgey committed
286
287
  public uploadSshKey(formData: FormData): void {
    this.http.post<SSHKey>('/backend/api/sshkey', formData).subscribe(
288
      (newKey: SSHKey) => {
Lukas Burgey's avatar
Lukas Burgey committed
289
290
        this.sshKeys.push(newKey);
        this.sshKeys$.next(this.sshKeys);
291
      },
292
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
293
294
295
    );
  }

296
  public removeSshKey(key: SSHKey) {
Lukas Burgey's avatar
Lukas Burgey committed
297
    console.log('Deleting key:', key);
Lukas Burgey's avatar
Lukas Burgey committed
298
299
300
301
302
303
304
305
306
307
308
    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);
        }
309
      },
310
      this.logErrorAndFetch,
Lukas Burgey's avatar
Lukas Burgey committed
311
312
    );
  }
313

314
  public changeDeployment(action: string, vo: VO): void {
315
316
    const body = {
      'type': action,
317
      'vo': vo.id,
318
    };
319
    this.http.post<Deployment>('/backend/api/deployments', body).pipe(
320
      catchError(this.catchError(true, "Error changing deployment")),
321
322
    ).subscribe(
      (dep: Deployment) => this.updateDeployment(dep),
323
324
325
326
327
    );
  }

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

Lukas Burgey's avatar
Lukas Burgey committed
332
333
  public subscribeSSHKeys(): Observable<SSHKey[]> {
    return this.sshKeys$.asObservable();
334
335
  }

336
337
  public subscribeUser(): Observable<User> {
    return this.user$.asObservable();
338
339
  }

340
341
  public subscribeDeployments(): Observable<Deployment[]> {
    return this.deployments$.asObservable();
342
343
  }

344
345
346
347
348
349
350
351
352
353
354
355
  public subscribeDeploymentStates(): Observable<DeploymentState[]> {
    return this.deploymentStates$.asObservable();
  }

  public subscribeStateFor(site: Site, service: Service): Observable<DeploymentState> {
    return this.deploymentStates$.asObservable().pipe(
      map((states: DeploymentState[]) => states.find(
          (dsi: DeploymentState) => dsi.site.id == site.id && dsi.service.id == service.id,
      )),
    );
  }

356
357
358
  public subscribeVOs(): Observable<VO[]> {
    let voSelector = (user: User) => user ? user.vos : [];
    return this.subscribeSpecific<VO[]>(voSelector);
359
360
  }

361
  public subscribeServiceDeployment(service: Service): Observable<Deployment> {
362
    return this.subscribeDeployments().pipe(
Lukas Burgey's avatar
Lukas Burgey committed
363
      map((deployments: Deployment[]) => {
364
365
          return deployments.find(
            (dep: Deployment) => dep.service ? dep.service.id == service.id : false
366
367
          );
        }
368
      ),
369
370
371
    );
  }

372
  public subscribeVODeployment(vo: VO): Observable<Deployment> {
373
374
375
    return this.subscribeDeployments().pipe(
      map((deployments: Deployment[]) => {
          return deployments.find(
Lukas Burgey's avatar
Lukas Burgey committed
376
            (dep: Deployment) => dep.vo ? dep.vo.id == vo.id : false
377
378
379
          );
        }
      ),
380
381
    );
  }
Lukas Burgey's avatar
Lukas Burgey committed
382
}