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
61
62
63
64
65
66
67
68
69
  private connect(): void {
    this.subscribeState().subscribe(
      (state: SAR) => {
        // 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
70

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Lukas Burgey's avatar
Lukas Burgey committed
194
      this.userStateUpdate(update.user_state);
195

196
197
198
199
200
201
      this.sar = update;
      this.sar$.next(update);

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

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

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

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

237
238
239
240
  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
241
242
  }

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

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

  public login(idp: t.IdP) {
266
    this.idpService.setIdPPreference(idp);
267

268
    window.location.href = '/backend/auth/v1/request';
269
270
  }

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

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

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

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

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

333

334
335
336
337
338
339
340
  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,
341
      this.logErrorAndFetch(),
342
343
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
    );
  }

  // 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
390
}