Commit b186c66c authored by Lukas Burgey's avatar Lukas Burgey

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[];
......
This diff is collapsed.
......@@ -3,7 +3,9 @@
<mat-panel-title style="min-width: 100px;">{{ group.name }}</mat-panel-title>
<mat-panel-description>{{ group.description?.description }}</mat-panel-description>
</mat-expansion-panel-header>
<div *ngIf="userService.getServices(group).length > 0; then serviceList else noServiceList"></div>
<!-- expansion body -->
<div *ngIf="services$$ | async; then serviceList else noServiceList"></div>
<ng-template #serviceList>
<p>
These services are provided for members of this VO.
......@@ -18,8 +20,8 @@
</tr>
</thead>
<tbody>
<ng-container *ngFor="let site of sites()">
<tr *ngFor="let service of servicesBySite(site)" >
<ng-container *ngFor="let site of sites$$ | async">
<tr *ngFor="let service of servicesAtSite(site) | async">
<td>
<span style="margin-right: 10px;" matTooltip="Site {{ site.name }} provides the service {{ service.name }} for members of {{ group.name }}">
<mat-icon>account_balance</mat-icon>
......@@ -32,8 +34,8 @@
{{ service.name }}
</span>
</td>
<td style="height: 42px;">
<span [ngSwitch]="stateItem(site, service)?.state" class="childs-inline">
<td *ngIf="(subscribeStateItem(site, service) | async) as stateItem; else noStateItem" style="height: 42px;">
<span [ngSwitch]="stateItem.state" class="childs-inline">
<mat-icon *ngSwitchCase="'deployed'" matTooltip="The credentials are deployed for the service {{ service.name }}. Click to see details.">call_made</mat-icon>
<mat-icon *ngSwitchCase="'questionnaire'" matTooltip="This site needs more data to deploy the keys. Please click to submit the data.">warning</mat-icon>
<mat-progress-spinner *ngSwitchCase="'deployment_pending'" diameter="24" mode="indeterminate" matTooltip="Waiting for the deployment of the credentials to the site {{ site.name }}"></mat-progress-spinner>
......@@ -44,29 +46,40 @@
<mat-icon *ngSwitchDefault mat-icon-button matTooltip="Access to this service was never requested.">call_received</mat-icon>
</span>
</td>
<td>
<span [ngSwitch]="stateItem(site, service)?.state">
<button *ngSwitchCase="'questionnaire'" (click)="dialog.openQuestionnaire(stateItem(site, service))" mat-raised-button class="mat-elevation-z6"> Questionnaire </button>
<button *ngSwitchCase="'deployed'" (click)="dialog.openCredentials(stateItem(site, service))" mat-raised-button class="mat-elevation-z6"> Credentials </button>
<button *ngSwitchCase="'failed'" (click)="dialog.openMessage(stateItem(site, service))" mat-raised-button class="mat-elevation-z6"> Failure </button>
<button *ngSwitchCase="'rejected'" (click)="dialog.openMessage(stateItem(site, service))" mat-raised-button class="mat-elevation-z6"> Rejected </button>
<td *ngIf="(subscribeStateItem(site, service) | async) as stateItem">
<span [ngSwitch]="stateItem.state">
<button *ngSwitchCase="'questionnaire'" (click)="dialog.openQuestionnaire(stateItem)" mat-raised-button class="mat-elevation-z6"> Questionnaire </button>
<button *ngSwitchCase="'deployed'" (click)="dialog.openCredentials(stateItem)" mat-raised-button class="mat-elevation-z6"> Credentials </button>
<button *ngSwitchCase="'failed'" (click)="dialog.openMessage(stateItem)" mat-raised-button class="mat-elevation-z6"> Failure </button>
<button *ngSwitchCase="'rejected'" (click)="dialog.openMessage(stateItem)" mat-raised-button class="mat-elevation-z6"> Rejected </button>
</span>
</td>
<ng-template #noStateItem>
<td>
<span>
<mat-icon mat-icon-button matTooltip="Access to this service was never requested.">call_received</mat-icon>
</span>
</td>
</ng-template>
</tr>
</ng-container>
</tbody>
</table>
</ng-template>
<ng-template #noServiceList>
<p *ngIf="deployment?.state_target != 'deployed'">
<div *ngIf="deployment$$ | async as deployment">
<p *ngIf="deployment.state_target == 'deployed'">
Currently, no services need membership of this VO. We will deploy your credentials to new services.
</p>
</div>
</ng-template>
<ng-template #noDeployment>
<p>
Currently, no services need membership of this VO. You can request a deployment anyway. We will deploy them to new services.
</p>
<p *ngIf="deployment?.state_target == 'deployed'">
Currently, no services need membership of this VO. We will deploy your credentials to new services.
</p>
</ng-template>
<mat-action-row>
<div [ngSwitch]="deployment?.state_target">
<div [ngSwitch]="(deployment$$ | async)?.state_target">
<span *ngSwitchCase="'not_deployed'" style="margin-right: 15px;">
You did not request access to services of this VO.
</span>
......@@ -74,7 +87,7 @@
You requested access to the services of this VO.
</span>
</div>
<div [ngSwitch]="deployment?.state_target">
<div [ngSwitch]="(deployment$$ | async)?.state_target">
<button *ngSwitchCase="'not_deployed'" (click)="changeDeployment('add')" mat-raised-button color="primary" matTooltip="Deploy credentials to services of VO {{ group.name }}">
Deploy my credentials
</button>
......
import { Component, OnInit, Input } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { UserService } from '../user.service';
import * as t from '../types/types.module';
@Component({
selector: 'app-vo-data',
templateUrl: './vo-data.component.html',
styleUrls: ['./vo-data.component.css']
})
export class VoDataComponent implements OnInit {
@Input() group: t.Group;
private services: t.Service[];
private services$ = new BehaviorSubject<t.Service[]>([]);
public services$$: Observable<t.Service[]>;
private sites: Map<number, t.Site> = new Map();
private sites$ = new BehaviorSubject<t.Site[]>([]);
public sites$$: Observable<t.Site[]>;
private deployment$ = <BehaviorSubject<t.Deployment>> new BehaviorSubject(new Object);
public deployment$$: Observable<t.Deployment>;
constructor(
public userService: UserService,
) {
}
ngOnInit(): void {
this.userService.subscribeSpecific(this.userService.serviceSelector).subscribe(
(services: t.Service[]) => {
// filter by the group of this vo
this.services = services.filter(
(s: t.Service) => s.groups.some(
(g: t.Group) => this.group.name == g.name
),
);
this.services$.next(this.services);
// generate the sites from the services
this.services.map(
(s: t.Service) => s.site.forEach(
(site: t.Site) => this.sites.set(site.id, site)
)
);
let uniqueSites = []
this.sites.forEach(
site => uniqueSites.push(site)
);
this.sites$.next(uniqueSites);
}
);
this.userService.subscribeGroupDeployment(this.group).subscribe(
(dep: t.Deployment) => {
if (dep){
this.deployment$.next(dep);
}
}
);
this.services$$ = this.services$.asObservable();
this.sites$$ = this.sites$.asObservable();
this.deployment$$ = this.deployment$.asObservable();
}
ngOnDestroy(): void {
this.services$.complete();
this.sites$.complete();
this.deployment$.complete();
}
public servicesAtSite(site: t.Site): Observable<t.Service[]> {
return this.services$$.map(
(services: t.Service[]) => services.filter(
(service: t.Service) => service.site.some(
(s: t.Site) => s.id === site.id
)
)
);
}
public subscribeStateItem(site: t.Site, service: t.Service): Observable<t.DeploymentStateItem> {
return this.deployment$$.map(
(dep: t.Deployment) => {
if (dep.state_items) {
return dep.state_items.find(
(dsi: t.DeploymentStateItem) => dsi.site.id == site.id && dsi.service.id == service.id,
)
}
},
);
}
public changeDeployment(action: string): void {
this.userService.changeDeployment(action, this.group).subscribe(
(dep: t.Deployment) => {
this.deployment$.next(dep);
}
);
}
}
import { Component, OnInit, Input } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DialogService } from '../dialogues/dialog.service';
import { UserService } from '../user.service';
import * as t from '../types/types.module';
@Component({
selector: 'app-vo',
templateUrl: './vo.component.html',
styleUrls: ['./vo.component.css']
})
export class VoComponent implements OnInit {
@Input() group: t.Group;
@Input() deployment: t.Deployment | undefined;
constructor(
public userService: UserService,
public http: HttpClient,
public dialog: DialogService,
) { }
ngOnInit() {
}
public deploymentMapped(): boolean {
return this.deployment != undefined;
}
public sites(): t.Site[] {
let sites: Map<number, t.Site> = new Map();
let ss:t.Site[] = [];
this.userService.getServices(this.group).map(
(s: t.Service) => {
s.site.forEach(
site => {
sites.set(site.id, site);
}
);
}
);
sites.forEach(
site => {
ss.push(site);
}
);
return ss;
}
private filterBySite(services: t.Service[], site: t.Site): t.Service[] {
return services.filter(
(s: t.Service) => {
return s.site.some(
(ss: t.Site) => {
return ss.id === site.id;
}
);
}
);
}
public servicesBySite(site: t.Site): t.Service[] {
if (this.deployment) {
return this.filterBySite(this.deployment.services, site);
} else {
return this.filterBySite(this.userService.getServices(this.group), site);
}
}
public services(): t.Service[] {
if (this.deployment) {
return this.deployment.services;
}
return this.userService.getServices(this.group);
}
public stateItem(site: t.Site, service: t.Service): t.DeploymentStateItem | undefined {
if (!this.deploymentMapped()) {
return undefined;
}
return this.deployment.state_items.find(
item => {
return item.service.id === service.id && item.service.site.some( s => { return s.id === s.id; });
}
);
}
public changeDeployment(action: string) {
const body = {
'type': action,
'group': this.group.id,
};
return this.http.post('/backend/api/deployments', body).subscribe(
(newDep: t.Deployment) => {
// update the deployment
this.deployment = newDep;
},
(err) => {
console.log(err);
this.userService.update();
}
);
}