Commit b186c66c authored by Lukas Burgey's avatar Lukas Burgey
Browse files

Make a lot of use of BehaviorSubject

parent 4fc0a000
......@@ -13,6 +13,7 @@ import {CookieService} from 'ngx-cookie-service';
// rxjs
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/filter';
import 'rxjs/add/observable/of';
// Our stuff
......@@ -27,12 +28,12 @@ import {MaterialModule} from './material/material.module';
import {SharedModule} from './shared/shared.module';
// declarations
import {AppComponent} from './app.component';
import {BodyComponent} from './body/body.component';
import {ServiceComponent} from './service/service.component';
import {HeaderComponent} from './header/header.component';
import {FooterComponent} from './footer/footer.component';
import { VoComponent } from './vo/vo.component';
import { AppComponent} from './app.component';
import { BodyComponent} from './body/body.component';
import { ServiceComponent} from './service/service.component';
import { HeaderComponent} from './header/header.component';
import { FooterComponent} from './footer/footer.component';
import { VoDataComponent } from './vo-data/vo-data.component';
@NgModule({
......@@ -56,7 +57,7 @@ import { VoComponent } from './vo/vo.component';
BodyComponent,
FooterComponent,
ServiceComponent,
VoComponent,
VoDataComponent,
],
providers: [
CookieService,
......
<div class="body" class="mat-typography" class="body">
<mat-tab-group *ngIf="userService.loggedIn()">
<mat-tab label="Your VOs">
<div class="mat-typography" class="body">
<h2>Your Virtual Organisations</h2>
<mat-accordion *ngIf="userService.user.groups.length > 0">
<app-vo *ngFor="let group of userService.getGroups()" [group]="group" [deployment]="userService.getDeploymentByGroup(group)"></app-vo>
</mat-accordion>
<p *ngIf="userService.user.groups.length == 0">
You are not a member in any group.
</p>
</div>
</mat-tab>
<mat-tab label="Services">
<div class="mat-typography" class="body">
<h2>Available Services</h2>
<mat-accordion *ngIf="userService.services.length > 0">
<app-service *ngFor="let service of userService.services" [deployment]="userService.getDeployment(service)" [service]="service"></app-service>
</mat-accordion>
<p *ngIf="userService.services.length === 0">
You have no available services.<br/>
This is due services requiring users to be member of a certain group.
</p>
</div>
</mat-tab>
<div class="body" class="mat-typography" class="body" >
<div *ngIf="userService.subscribeLoggedIn() | async">
<mat-tab-group>
<mat-tab label="Your VOs">
<div class="mat-typography" class="body">
<h2>Your Virtual Organisations</h2>
<div *ngIf="(userService.subscribeSpecific(userService.groupSelector) | async) as groups; else noGroups">
<mat-accordion *ngIf="groups.length > 0; else noGroups">
<app-vo-data *ngFor="let group of groups" [group]="group"></app-vo-data>
</mat-accordion>
</div>
<ng-template #noGroups>
<p>
You are not member in any Virtual Organisations.
</p>
</ng-template>
</div>
</mat-tab>
<!--
<mat-tab label="Services">
<div class="mat-typography" class="body">
<h2>Available Services</h2>
<mat-accordion *ngIf="userService.services$ | async as services; else noServices">
<app-service *ngFor="let service of services" [deployment]="userService.getDeployment(service)" [service]="service"></app-service>
</mat-accordion>
<ng-template #noServices>
<p>
You have no available services.<br/>
This is due services requiring users to be member of a certain group.
</p>
</ng-template>
</div>
</mat-tab>
-->
</mat-tab-group>
</div>
</div>
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { UserService } from '../user.service';
import * as t from '../types/types.module';
@Component({
......@@ -10,8 +13,9 @@ import { UserService } from '../user.service';
})
export class BodyComponent implements OnInit {
constructor(
public userService: UserService
public userService: UserService,
) {
}
......
......@@ -22,13 +22,17 @@ export class DialogService {
};
constructor(
public dialog: MatDialog,
private dialog: MatDialog,
) { }
public openProfile() {
public openProfile(user: t.User) {
this.profileDialog = this.dialog.open(
ProfileComponent,
this.settings,
{
data: {
user: user,
}
}
);
}
......
<div class="mat-typography">
<h3>User Info</h3>
<mat-table [dataSource]="userService.userInfo()">
<mat-table [dataSource]="dataSource()">
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.name}}</mat-cell>
......
import { Component, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material';
import { UserService } from '../../user.service';
import { Component, OnInit, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatTableDataSource } from '@angular/material';
import {} from '@angular/material';
@Component({
selector: 'app-profile',
......@@ -12,10 +11,19 @@ export class ProfileComponent implements OnInit {
columns = ['name', 'info'];
constructor(
public userService: UserService,
@Inject(MAT_DIALOG_DATA) public data: any,
) { }
ngOnInit() {
}
dataSource(): MatTableDataSource<any> {
const userInfoList = [];
for (const key in this.data.user.userinfo) {
if (this.data.user.userinfo.hasOwnProperty(key)) {
userInfoList.push({name: key, info: this.data.user.userinfo[key]});
}
}
return new MatTableDataSource(userInfoList);
}
}
<mat-toolbar color="primary" class="mat-typography">
<mat-toolbar-row>
<h1>
Federated User Credential Deployment Portal
</h1>
<div class="header-bar">
<span *ngIf="userService.loggedIn() ? false : true">
<form (ngSubmit)="userService.login(selectedIdP)" #loginForm="ngForm">
<mat-form-field>
<mat-select name="idp" required [(ngModel)]="selectedIdP">
<mat-option *ngFor="let idp of idps$ | async" [value]="idp">
{{ idp.name }}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button [disabled]="!loginForm.form.valid" color="accent" type="submit">Login</button>
</form>
</span>
<span *ngIf="userService.loggedIn()">
<button mat-button mat-icon-button (click)="dialog.openAccount()">
<mat-icon>settings</mat-icon>
</button>
<!--
<button mat-button mat-icon-button (click)="dialog.openSshKeys()">
<mat-icon>vpn_key</mat-icon>
</button>
-->
<button *ngIf="userService.user.profile_name != undefined" mat-button (click)="dialog.openProfile()">
{{ userService.user.profile_name }}
</button>
<button *ngIf="userService.user.profile_name == undefined" mat-button (click)="dialog.openProfile()">
Profile
</button>
<button mat-raised-button color="accent" (click)="userService.logout()">Logout</button>
</span>
</div>
</mat-toolbar-row>
<mat-toolbar-row>
<h1>
Federated User Credential Deployment Portal
</h1>
<div class="header-bar">
<div *ngIf="(userService.subscribeLoggedIn() | async); else notLoggedIn">
<span *ngIf="(userService.subscribeSpecific(userService.userSelector) | async) as user">
<button mat-button mat-icon-button (click)="dialog.openAccount()">
<mat-icon>settings</mat-icon>
</button>
<button *ngIf="user.profile_name != undefined" mat-button (click)="dialog.openProfile(user)">
{{ user.profile_name }}
</button>
<button *ngIf="user.profile_name == undefined" mat-button (click)="dialog.openProfile(user)">
Profile
</button>
<button mat-raised-button color="accent" (click)="userService.logout()">
Logout
</button>
</span>
</div>
<ng-template #notLoggedIn>
<span>
<form (ngSubmit)="userService.login(selectedIdP)" #loginForm="ngForm">
<mat-form-field>
<mat-select name="idp" required [(ngModel)]="selectedIdP">
<mat-option *ngFor="let idp of idps$ | async" [value]="idp">
{{ idp.name }}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button [disabled]="!loginForm.form.valid" color="accent" type="submit">Login</button>
</form>
</span>
</ng-template>
</div>
</mat-toolbar-row>
</mat-toolbar>
......@@ -20,20 +20,18 @@ export class HeaderComponent implements OnInit {
public selectedIdP: IdP;
constructor(
public idpService: IdpService,
public userService: UserService,
private idpService: IdpService,
public dialog: DialogService,
public userService: UserService,
) {
this.idps$ = this.idpService.subscribeIdps();
this.selectedIdP$ = this.idpService.subscribeSelectedIdp();
if (!userService.loggedIn()) {
this.selectedIdP$.subscribe(
(idp: IdP) => {
this.selectedIdP = idp;
}
);
}
this.selectedIdP$.subscribe(
(idp: IdP) => {
this.selectedIdP = idp;
}
);
}
ngOnInit() {
......
......@@ -48,10 +48,6 @@ export class ServiceComponent implements OnInit {
// update the deployment
this.deployment = newDep;
},
(err) => {
console.log(err);
this.userService.update();
}
);
}
}
......@@ -29,6 +29,8 @@ export interface Group {
description: GroupDescription;
}
export type GroupMap = Map<Group, Service[]>
export interface Site {
id: number;
name: string;
......@@ -87,10 +89,14 @@ export interface Deployment {
state_target: string
}
export interface UserInfo {
groups: string[];
}
export interface User {
id: number;
profile_name: string;
userinfo: any;
userinfo: UserInfo;
ssh_keys: SSHKey[];
services: Service[];
groups: Group[];
......
......@@ -4,6 +4,7 @@ import {MatTableDataSource} from '@angular/material';
import {Observable} from 'rxjs/Observable';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Subject} from 'rxjs/Subject';
import {CookieService} from 'ngx-cookie-service';
import {StompConfig, StompRService} from '@stomp/ng2-stompjs';
......@@ -12,29 +13,29 @@ import {Message} from '@stomp/stompjs';
import {SnackBarService} from './snackbar.service';
import {IdpService} from './idp.service';
import * as t from './types/types.module';
import { StateAPIResult as SAR } from './types/types.module';
@Injectable()
export class UserService {
private _loggedIn: boolean = false;
private initialized = false;
private loggedIn: boolean = false;
private loggedIn$ = new BehaviorSubject<boolean>(false);
private user: t.User;
private user$ = <BehaviorSubject<t.User>> new BehaviorSubject(new Object);
public userState: t.UserState;
public userState$ = <BehaviorSubject<t.UserState>> new BehaviorSubject(new Object);
private sar: SAR;
private sar$ = <BehaviorSubject<SAR>> new BehaviorSubject(new Object);
private sshKeys: t.SSHKey[] = new Array<t.SSHKey>();
private sshKeys$ = <BehaviorSubject<t.SSHKey[]>> new BehaviorSubject([]);
private sshKeys$ = new BehaviorSubject<t.SSHKey[]>(this.sshKeys);
private messages: string[] = [];
private userState: t.UserState;
private userState$ = <BehaviorSubject<t.UserState>> new BehaviorSubject(new Object);
// local copy of services
private services: t.Service[] = [];
private messages: string[] = [];
public groupMap: Map<string, t.Service[]> = new Map();
public groupsParsed: boolean = false;
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 : [];
constructor(
private cookieService: CookieService,
......@@ -43,30 +44,69 @@ export class UserService {
private idpService: IdpService,
private _stompService: StompRService,
) {
this.update();
this.connect();
}
// DATA SERVICE API
public subscribeUserState(): Observable<t.UserState> {
return this.userState$.asObservable();
}
// PRIVATE API
private disconnect(): void {
this.loggedIn = false;
this.sshKeys$.complete();
public subscribeUser(): Observable<t.User> {
return this.user$.asObservable();
this.loggedIn$.next(this.loggedIn);
}
public subscribeSSHKeys(): Observable<t.SSHKey[]> {
return this.sshKeys$.asObservable();
}
private connect(): void {
this.subscribeState().subscribe(
(state: SAR) => {
//console.log('sar$ yielded', state);
// 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);
public subscribeLoggedIn(): Observable<boolean> {
return this.loggedIn$.asObservable();
},
this.errorLogger,
() => 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');
}
},
this.errorLogger,
() => console.log('loggedIn$ completed'),
);
// start pulling updates
this.fetch();
}
// PRIVATE API
private initStomp() {
private connectLiveUpdates(user: t.User): void {
// handle with care
let login = this.user.id
let login = user.id
let passcode = this.cookieService.get('sessionid');
const stompConfig: StompConfig = {
......@@ -94,12 +134,9 @@ export class UserService {
};
this._stompService.config = stompConfig;
this._stompService.initAndConnect();
}
private connectLiveUpdates() {
this.initStomp();
let subscription = this._stompService.subscribe(
'/exchange/users/' + this.user.id.toString()
'/exchange/users/' + user.id.toString()
);
subscription.subscribe(
......@@ -110,6 +147,7 @@ export class UserService {
this.messages.push(update.error);
}
if (update.user_state) {
console.log('stomp received stateAPIUpdate', update.user_state);
this.updateUserState(update.user_state);
}
},
......@@ -119,114 +157,61 @@ export class UserService {
);
}
private updateUser(newUser: t.User) {
this.user = newUser;
this.user$.next(this.user);
this.sshKeys = this.user.ssh_keys;
this.sshKeys$.next(this.sshKeys);
}
private updateServices(newServices: t.Service[]){
if (this.services.length !== newServices.length) {
this.services = [];
for (let service of newServices) {
this.services.push(service);
}
for (let group of this.user.groups) {
this.groupMap.set(group.name, this._getServices(group.name));
}
this.groupsParsed = true;
}
}
private updateUserState(newState: t.UserState) {
private updateUserState(newState: t.UserState): void {
// did a login occur?
let login = (!this._loggedIn && newState);
let login = (!this.loggedIn && newState);
// did a logout occur?
let logout = (this._loggedIn && !newState);
let logout = (this.loggedIn && !newState);
// -- Value updating --
if (login) {
this._loggedIn = true;
this.snackBar.open('Logged in');
this.connectLiveUpdates();
this.loggedIn = true;
this.loggedIn$.next(this.loggedIn);
}
if (logout) {
this._logout()
this.loggedIn = false;
this.loggedIn$.next(this.loggedIn);
return
}
this.userState = newState;
this.userState$.next(this.userState);
}
private _logout() {
this._loggedIn = false;
this.snackBar.open('Logged out');
this._stompService.disconnect();
this.services = [];
this.user = null;
this.userState = null;
this.userState$.complete();
if (newState) {
this.userState = newState;
this.userState$.next(newState);
}
}
private stateAPIUpdate(update: t.StateAPIResult) {
console.log('stateAPIUpdate:', update);
if (update) {
this.updateUser(update.user)
this.updateUserState(update.user_state);
this.updateServices(update.services);
// report an occured error
if (update.error) {
this.snackBar.open(update.error);
this.errorLogger(update.error);
}
} else {
console.log("Got null update");
}
}
private _getServices(groupName: string) : t.Service[] {
let services: t.Service[] = [];
return this.services.filter(
(service: t.Service) => {
return service.groups.some(
(group : t.Group) => {
return group.name === groupName;
}
);
if (!update.user) {
this.disconnect();
return
}
);
}
private getDeploymentByGroup(group: t.Group): t.Deployment | undefined {
let dep = this.userState.deployments.find(
(d: t.Deployment) => {
return d.group === group.id;
}
);
return dep
}
this.updateUserState(update.user_state);
// PUBLIC API
public loggedIn(): boolean {
if (this.userState) {
return true;
this.sar = update;
this.sar$.next(update);
} else {
this.disconnect();
return
}
return false;
}
public update(): void {
private fetch(): void {
this.http.get('/backend/api/state').subscribe(
(data: t.StateAPIResult) => this.stateAPIUpdate(data),