Commit a07a5a1e authored by Lukas Burgey's avatar Lukas Burgey

Do a major refactoring

parent bcb8174b
<div class="mat-typography">
<h3>User Info</h3>
<mat-table [dataSource]="userService.userInfoData">
<mat-table [dataSource]="userService.userInfo()">
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.name}}</mat-cell>
......
......@@ -6,7 +6,7 @@
<div *ngIf="upload; then uploading else not_uploading"></div>
</div>
<ng-template #key_list>
<mat-table [dataSource]="userService.sshKeyData">
<mat-table [dataSource]="userService.sshKeyTable()">
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.name}}</mat-cell>
......
<div>
<span *ngIf="userService.loggedIn ? false : true">
<span *ngIf="userService.userState ? false : true">
<form *ngIf="idps"
(ngSubmit)="userService.login(selectedIdP)" #loginForm="ngForm">
<mat-form-field>
......@@ -12,7 +12,7 @@
<button mat-raised-button [disabled]="!loginForm.form.valid" color="accent" type="submit">Login</button>
</form>
</span>
<span *ngIf="userService.loggedIn">
<span *ngIf="userService.userState">
<button mat-button mat-icon-button (click)="dialog.openAccount()">
<mat-icon>settings</mat-icon>
</button>
......
<div *ngIf="userService.loggedIn">
<div *ngIf="userService.loggedIn()">
<div style="padding-bottom: 30px;">
<h2>Services</h2>
<div *ngIf="userService.services.length > 0">
<mat-accordion>
<app-service *ngFor="let service of userService.services"
[serviceData]="service"></app-service>
[service]="service"></app-service>
</mat-accordion>
</div>
<p *ngIf="userService.services?.length == 0">
......
<mat-expansion-panel *ngIf="serviceData">
<mat-expansion-panel *ngIf="service">
<mat-expansion-panel-header>
<mat-panel-title>{{ serviceData.name }}</mat-panel-title>
<mat-panel-description>{{ serviceData.description }}</mat-panel-description>
<mat-panel-title>{{ service.name }}</mat-panel-title>
<mat-panel-description>{{ service.description }}</mat-panel-description>
</mat-expansion-panel-header>
<table width="60%">
<tr style="margin-bottom: 15px;">
<td *ngFor="let site of serviceData.site">
<td *ngFor="let site of service.site">
<span matTooltip="Provided at site {{ site.name }}" style="margin-right: 10px;">
<mat-icon style="vertical-align: middle; padding-right: 5px;">storage</mat-icon>
{{ site.name }}
</span>
<div *ngIf="userService.user.ssh_keys.length > 0">
<span *ngIf="userService.user.ssh_keys.length > 0">
<span *ngFor="let key of userService.user.ssh_keys"
[ngSwitch]="userService.taskState(site, serviceData, key)" class="childs-inline">
[ngSwitch]="userService.taskState(site, service, key)" class="childs-inline" style="margin-right: 5px;">
<!-- states with actions -->
<button *ngSwitchCase="'deployed'" mat-button mat-icon-button
matTooltip="The credentials are deployed to the site.">
matTooltip="The key {{key.name}} is deployed to the site. Click to see details.">
<mat-icon style="vertical-align: middle">call_made</mat-icon>
</button>
<button *ngSwitchCase="'questionnaire'" mat-button mat-icon-button
(click)="dialog.openQuestionnaire(userService.taskItem(site, serviceData))"
matTooltip="This site needs more data to deploy the credentials. Please click to
submit the data.">
(click)="dialog.openQuestionnaire(userService.taskItem(site, service))"
matTooltip="This site needs more data to deploy the keys. Please click to submit the data.">
<mat-icon>warning</mat-icon>
</button>
<!-- states without actions -->
<span *ngSwitchCase="'deployment_pending'"
matTooltip="Waiting for the deployment of the credentials from the site">
<span *ngSwitchCase="'deployment_pending'" matTooltip="Waiting for the deployment of the key {{ key.name }} by the site">
<mat-progress-spinner diameter="24" mode="indeterminate"></mat-progress-spinner>
</span>
<span *ngSwitchCase="'removal_pending'"
matTooltip="Waiting for the removal of the credentials from the site">
<span *ngSwitchCase="'removal_pending'" matTooltip="Waiting for the removal of the key {{ key.name }} from the site">
<mat-progress-spinner diameter="24" mode="indeterminate"></mat-progress-spinner>
</span>
<span *ngSwitchCase="'not_deployed'"
matTooltip="The credential is not deployed to this site.">
<span *ngSwitchCase="'not_deployed'" matTooltip="The key {{ key.name }} is not deployed to this site.">
<mat-icon style="vertical-align: middle">call_received</mat-icon>
</span>
<span *ngSwitchCase="'failed'"
matTooltip="This site failed to deploy the credentials. The deployment will be retried.">
<span *ngSwitchCase="'failed'" matTooltip="This site failed to deploy the credentials. The deployment will be retried.">
<mat-icon style="vertical-align: middle">error</mat-icon>
</span>
<span *ngSwitchCase="'rejected'"
matTooltip="This site rejected the deployment. You can not use this service.">
<span *ngSwitchCase="'rejected'" matTooltip="This site rejected the deployment of the key {{ key.name }}.">
<mat-icon style="vertical-align: middle">error</mat-icon>
</span>
<span *ngSwitchDefault
matTooltip="Unrecognized state">
<mat-icon style="vertical-align: middle">details</mat-icon>
<span *ngSwitchDefault matTooltip="Unrecognized state">
<mat-icon style="vertical-align: middle">error</mat-icon>
</span>
</span>
</div>
</span>
</td>
</tr>
<tr>
<td style="padding-right: 35px;"
*ngFor="let group of serviceData.groups"
*ngFor="let group of service.groups"
matTooltip="Can be used with membership of group {{ group.name }}">
<mat-icon style="vertical-align: middle; padding-right: 5px;">lock outline</mat-icon>
{{ group.name }}
......
......@@ -4,13 +4,17 @@ import { MatCheckboxChange } from '@angular/material/checkbox';
import { UserService } from '../user.service';
import { DialogService } from '../dialogues/dialog.service';
import * as t from '../types/types.module';
@Component({
selector: 'app-service',
templateUrl: './service.component.html',
styleUrls: ['./service.component.css']
})
export class ServiceComponent implements OnInit {
@Input() serviceData: any;
@Input() service: t.Service;
public sshKeys: t.SSHKeyRef[];
public deployment: t.Deployment;
constructor(
public userService: UserService,
......@@ -19,22 +23,17 @@ export class ServiceComponent implements OnInit {
}
ngOnInit() {
}
public getDeployment() {
const deployment = this.userService.user.deployments.find(
this.sshKeys = this.userService.user.ssh_keys;
this.deployment = this.userService.userState.deployments.find(
d => {
return d.service.name === this.serviceData.name;
return d.service.id === this.service.id;
}
);
return deployment;
}
public isDeployed(key): boolean {
const deployment = this.getDeployment();
if (deployment) {
return deployment.ssh_keys.some(k => {
public isDeployed(key: t.SSHKeyRef): boolean {
if (this.deployment) {
return this.deployment.ssh_keys.some(k => {
return k.id === key.id;
});
}
......@@ -43,9 +42,9 @@ export class ServiceComponent implements OnInit {
public deploymentChange(key) {
if (!this.isDeployed(key)) {
this.userService.addDeployment(this.serviceData, key);
this.userService.addDeployment(this.service, key);
} else {
this.userService.removeDeployment(this.serviceData, key);
this.userService.removeDeployment(this.service, key);
}
}
}
......@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
// auth stuff
export interface IdP {
id: number;
name: string;
......@@ -17,7 +18,13 @@ export interface AllAuthInfo {
selected: IdP;
}
interface Group {
// user stuff
export interface Group {
name: string;
}
export interface Site {
id: number;
name: string;
}
......@@ -32,35 +39,58 @@ export interface SSHKey {
key: string;
}
export interface SSHKeyRef {
id: number;
name: string;
}
export interface Service {
id: number;
name: string;
sites: Site[];
group: Group[];
}
interface Deployment {
export interface DeploymentStateItem {
id: number;
site: Site;
state: string;
questionnaire: any;
credentials: any;
key: SSHKeyRef;
service: Service;
ssh_keys: SSHKey[];
}
export interface DeploymentState {
id: number;
service: Service;
state_items: DeploymentStateItem[];
}
export interface Deployment {
service: Service;
ssh_keys: SSHKeyRef[];
ssh_keys_to_withdraw: SSHKeyRef[];
states: DeploymentState[];
}
export interface User {
email: string;
userinfo: any;
ssh_keys: SSHKey[];
deployments: Deployment[];
deployment_states: any[];
deployment_state_items: any[];
services: Service[];
id: number;
}
interface StateAPI {
services: Service[];
logged_in: boolean;
export interface UserState {
deployments: Deployment[];
deployment_states: DeploymentState[];
deployment_state_items: DeploymentStateItem[];
}
export interface UserState {
services: Service[];
logged_in: boolean;
export interface StateAPIResult {
user: User;
user_state: UserState;
services: Service[];
error: string;
}
......
import { TestBed, inject } from '@angular/core/testing';
import { UserService } from './user.service';
describe('UserService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [UserService]
});
});
it('should be created', inject([UserService], (service: UserService) => {
expect(service).toBeTruthy();
}));
});
......@@ -13,17 +13,17 @@ import {StompConfig, StompRService} from '@stomp/ng2-stompjs';
import {Message} from '@stomp/stompjs';
import {environment} from '../environments/environment';
import {IdP, User, Service, AuthInfo, AllAuthInfo, UserState, SSHKey, NewSSHKey} from './types/types.module';
import * as t from './types/types.module';
@Injectable()
export class UserService {
public loggedIn = false;
public user: User;
public sshKeyData: MatTableDataSource<any>;
public userInfoData: MatTableDataSource<any>;
public services: Service[];
private _loggedIn: boolean = false;
public user: t.User;
public userState: t.UserState;
public messages: string[] = [];
public services: t.Service[];
constructor(
......@@ -32,191 +32,239 @@ export class UserService {
public snackBar: SnackBarService,
private _stompService: StompRService,
) {
this.updateState();
this.update();
}
public setIdPPreference(idp: IdP) {
this.cookieService.set(environment.idpCookieName, String(idp.id));
}
// PRIVATE API
private initStomp() {
// handle with care
let login = this.user.id
let passcode = this.cookieService.get('sessionid');
public errorHandler(error: any): void {
if (error.status === 500) {
this.snackBar.open('Server Error');
}
}
const stompConfig: StompConfig = {
// Which server?
url: 'wss://hdf-portal.data.kit.edu/ws',
public getIdPPreference(): Observable<AllAuthInfo> {
let idpID = Number(this.cookieService.get(environment.idpCookieName));
// Headers
// Typical keys: login, passcode, host
headers: {
login: 'webpage-client:' + login,
passcode: passcode,
},
return this.http.get('/backend/auth/v1/info').map(
(authInfo: AuthInfo) => {
let selected = authInfo.idps[1];
// 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
// Typical value 5000 (5 seconds)
reconnect_delay: 5000,
if (!idpID) {
idpID = authInfo.default;
}
for (const idp of authInfo.idps) {
if (idp.id === idpID) {
selected = idp;
}
}
return {
idps: authInfo.idps,
selected: selected,
};
}
).catch(
(error: any) => {
this.errorHandler(error);
return Observable.of(null);
}
);
// Will log diagnostics on console
debug: false,
};
this._stompService.config = stompConfig;
this._stompService.initAndConnect();
}
private connectLiveUpdates() {
this.initStomp();
let subscription = this._stompService.subscribe(
'/exchange/update/' + this.user.id.toString()
);
private _updateUserValues(newUser: any) {
this.user = newUser;
if (this.user) {
// build sshKeyData
if ('ssh_keys' in this.user) {
this.sshKeyData = new MatTableDataSource(this.user.ssh_keys);
} else {
this.sshKeyData = undefined;
subscription.map((message: Message) => {
return message.body;
}).subscribe((body: any) => {
let json = JSON.parse(body);
if (json.message && json.message != '') {
this.snackBar.open(json.message);
this.messages.push(json.message);
}
// build userInfoData
const userInfoList = [];
for (const key in this.user.userinfo) {
if (this.user.userinfo.hasOwnProperty(key)) {
userInfoList.push({name: key, info: this.user.userinfo[key]});
}
if (json.user_state) {
this.updateUserState(json.user_state);
}
this.userInfoData = new MatTableDataSource(userInfoList);
});
}
} else {
this.sshKeyData = undefined;
this.userInfoData = undefined;
}
private updateUser(newUser: t.User) {
this.user = newUser;
}
public updateData(newData: any) {
// did a login occur?
let login = (!this.loggedIn && newData.logged_in);
private updateUserState(newState: t.UserState) {
// did a login occur?
let login = (!this._loggedIn && newState);
// did a logout occur?
let logout = (this.loggedIn && !newData.logged_in);
// report an occured error
if (newData.error) {
this.snackBar.open(newData.error);
}
let logout = (this._loggedIn && !newState);
// -- Value updating --
this.loggedIn = newData.logged_in;
// this check is needed because the accordeon collapses if we overwrite the list
if (!this.services) {
this.services = newData.services;
}
this._updateUserValues(newData.user);
if (login) {
this._loggedIn = true;
this.snackBar.open('Logged in');
this.connectLiveUpdates()
}
if (logout) {
this._loggedIn = false;
this.snackBar.open('Logged out');
this._stompService.disconnect();
}
this.userState = newState;
}
private stateAPIUpdate(update: t.StateAPIResult) {
this.updateUser(update.user)
this.updateUserState(update.user_state);
this.services = update.services;
// reset some attributes
this.services = [];
// report an occured error
if (update.error) {
this.snackBar.open(update.error);
}
}
public updateState() {
private setIdPPreference(idp: t.IdP) {
this.cookieService.set(environment.idpCookieName, String(idp.id));
}
// PUBLIC API
public userInfo(): MatTableDataSource<any> {
const userInfoList = [];
for (const key in this.user.userinfo) {
if (this.user.userinfo.hasOwnProperty(key)) {
userInfoList.push({name: key, info: this.user.userinfo[key]});
}
}
return new MatTableDataSource(userInfoList);
}
public sshKeyTable(): MatTableDataSource<any> {
return new MatTableDataSource(this.user.ssh_keys);
}
public loggedIn():boolean {
if (this.userState) {
return true;
}
return false;
}
public update() {
this.http
.get('/backend/api/state')
.subscribe(
(data: UserState) => {
this.updateData(data);
(data: t.StateAPIResult) => {
this.stateAPIUpdate(data);
},
(err: HttpErrorResponse) => {
if (!this.services) {
this.services = [];
}
console.log('Error', err);
this.snackBar.open('Error receiving data from the server');
}
);
}
public errorHandler(error: any): void {
if (error.status === 500) {
this.snackBar.open('Server Error');
}
}
public getIdPPreference(): Observable<t.AllAuthInfo> {
let idpID = Number(this.cookieService.get(environment.idpCookieName));
return this.http.get('/backend/auth/v1/info').map(
(authInfo: t.AuthInfo) => {
let selected = authInfo.idps[1];
if (!idpID) {
idpID = authInfo.default;
}
for (const idp of authInfo.idps) {
if (idp.id === idpID) {
selected = idp;
}
}
return {
idps: authInfo.idps,
selected: selected,
};
}
).catch(
(error: any) => {
this.errorHandler(error);
return Observable.of(null);
}
);
}
public login(idp: IdP) {
public login(idp: t.IdP) {
this.setIdPPreference(idp);
window.location.href = 'https://hdf-portal.data.kit.edu/backend/auth/v1/request';
}
public logout() {
this.http.post('/backend/auth/v1/logout', {}).subscribe(
data => {
this.updateData(data);
(data: t.StateAPIResult) => {
this.stateAPIUpdate(data);
}
);
}
public addSshKey(key: NewSSHKey) {
public addSshKey(key: t.NewSSHKey) {
const body = {
'type': 'add',
'key': key,
};
return this.http.post('/backend/api/sshkey', body).subscribe(
data => {
this.updateData(data);
(data: t.UserState) => {
this.updateUserState(data);
},
(err) => {
this.snackBar.open('Error uploading key');
console.log(err);
this.updateState();
this.update();
}
);
}
public removeSshKey(key: SSHKey) {
public removeSshKey(key: t.SSHKey) {
const body = {
'type': 'remove',
'id': key.id,
};
return this.http.post('/backend/api/sshkey', body).subscribe(
(data) => {
this.updateData(data);
(data: t.UserState) => {
this.updateUserState(data);
},
(err) => {
this.snackBar.open('Error deleting key');
console.log(err);
this.updateState();
this.update();
}
);
}
public addDeployment(service: Service, key: SSHKey) {
public addDeployment(service: t.Service, key: t.SSHKey) {
const body = {
'type': 'add',
'key': key.id,
'service': service.id,
};
return this.http.post('/backend/api/deployments', body).subscribe(
(data) => {
(data: t.UserState) => {
//this.snackBar.open('Deployed key ' + key.name);
this.updateData(data);
this.updateUserState(data);
},
(err) => {
this.snackBar.open('Error deploying key ' + key.name);
console.log(err);
this.updateState();
this.update();
}
);
}
public removeDeployment(service: Service, key: SSHKey) {
public removeDeployment(service: t.Service, key: t.SSHKey) {
const body = {
'type': 'remove',
'key': key.id,
......@@ -224,102 +272,50 @@ export class UserService {
};
return this.http.post('/backend/api/deployments', body).subscribe(
(data) => {
(data: t.UserState) => {
//this.snackBar.open('Withdrew key ' + key.name);
this.updateData(data);
this.updateUserState(data);
},
(err) => {
this.snackBar.open('Error withdrawing key ' + key.name);
console.log(err);
this.updateState();
this.update();
}
);
}
public sentQuestionnaire(taskItemID: number, answers: any) {
return this.http.post('/backend/api/questionnaire?id='+String(taskItemID), answers).subscribe(
(data) => {
(data: t.UserState) => {
this.snackBar.open('Uploaded questionnaire');
//this.snackBar.open('Deployed key ' + key.name);
this.updateData(data);
this.updateUserState(data);
},
(err) => {
this.snackBar.open('Error uploading questionnaire');
console.log(err);
this.updateState();
this.update();
}
);
}
public deleteUser() {
return this.http.delete('/backend/api/delete_user/').subscribe(
(data) => {
(data: t.UserState) => {
this.snackBar.open('Deleted user from server');
this.updateData(data);
this.updateUserState(data);
},
(err) => {
this.snackBar.open('Error deleting user from server');
console.log(err);
this.updateState();
this.update();
}
);
}
public initStomp() {
// handle with care
let login = this.user.id
let passcode = this.cookieService.get('sessionid');
const stompConfig: StompConfig = {
// Which server?
url: 'wss://hdf-portal.data.kit.edu/ws',
// Headers
// Typical keys: login, passcode, host
headers: {
login: 'webpage-client:' + login,
passcode: passcode,
},