Commit b53457d3 authored by Lukas Burgey's avatar Lukas Burgey

Fix interaction with the backend

parent c7091df6
<div class="mat-typography">
<h2>Credentials</h2>
<p>
You can access the service {{ stateItem.service.name }} at {{ stateItem.site.name }} using the SSH Key {{ stateItem.key.name }}.
</p>
<div *ngIf="credentialCount > 0">
<p>
Additional information about accessing the service is displayed below.
</p>
<mat-table [dataSource]="table">
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.name}}</mat-cell>
</ng-container>
<ng-container matColumnDef="value">
<mat-header-cell *matHeaderCellDef>Value</mat-header-cell>
<mat-cell *matCellDef="let element">{{ element.value }}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="columns"></mat-header-row>
<mat-row *matRowDef="let row; columns: columns;"></mat-row>
</mat-table>
</div>
</div>
import {Component, OnInit,Inject} from '@angular/core';
import {MAT_DIALOG_DATA} from '@angular/material';
import {MatTableDataSource} from '@angular/material';
import { UserService } from '../../user.service';
import * as t from '../../types/types.module';
@Component({
selector: 'app-credentials',
templateUrl: './credentials.component.html',
styleUrls: ['./credentials.component.css']
})
export class CredentialsComponent implements OnInit {
public columns = ["name", "value"];
public stateItem: t.DeploymentStateItem;
public credentialCount: number = 0;
public table: MatTableDataSource<any>;
constructor(
public userService: UserService,
@Inject(MAT_DIALOG_DATA) public data: any,
) {
this.stateItem = data.stateItem;
}
ngOnInit() {
const credentialList = [];
for (const key in this.stateItem.credentials) {
if (this.stateItem.credentials.hasOwnProperty(key)) {
credentialList.push(
{
name: key,
value: this.stateItem.credentials[key],
}
);
}
}
this.credentialCount = credentialList.length;
this.table = new MatTableDataSource(credentialList);
}
}
...@@ -5,6 +5,9 @@ import { ProfileComponent } from './profile/profile.component'; ...@@ -5,6 +5,9 @@ import { ProfileComponent } from './profile/profile.component';
import { SshKeysComponent } from './ssh-keys/ssh-keys.component'; import { SshKeysComponent } from './ssh-keys/ssh-keys.component';
import { AccountComponent } from './account/account.component'; import { AccountComponent } from './account/account.component';
import { QuestionnaireComponent } from './questionnaire/questionnaire.component'; import { QuestionnaireComponent } from './questionnaire/questionnaire.component';
import { CredentialsComponent } from './credentials/credentials.component';
import * as t from '../types/types.module';
@Injectable() @Injectable()
export class DialogService { export class DialogService {
...@@ -12,6 +15,7 @@ export class DialogService { ...@@ -12,6 +15,7 @@ export class DialogService {
sshKeysDialog: MatDialogRef<any>; sshKeysDialog: MatDialogRef<any>;
accountDialog: MatDialogRef<any>; accountDialog: MatDialogRef<any>;
questionnaireDialog: MatDialogRef<any>; questionnaireDialog: MatDialogRef<any>;
credentialsDialog: MatDialogRef<any>;
settings = { settings = {
width: '80%', width: '80%',
...@@ -42,12 +46,23 @@ export class DialogService { ...@@ -42,12 +46,23 @@ export class DialogService {
); );
} }
public openQuestionnaire(taskItem: any) { public openQuestionnaire(stateItem: t.DeploymentStateItem) {
this.questionnaireDialog = this.dialog.open( this.questionnaireDialog = this.dialog.open(
QuestionnaireComponent, QuestionnaireComponent,
{ {
data: { data: {
taskItem: taskItem, stateItem: stateItem,
}
}
);
}
public openCredentials(stateItem: t.DeploymentStateItem) {
this.credentialsDialog = this.dialog.open(
CredentialsComponent,
{
data: {
stateItem: stateItem,
} }
} }
); );
......
...@@ -11,6 +11,7 @@ import {QuestionnaireComponent} from './questionnaire/questionnaire.component'; ...@@ -11,6 +11,7 @@ import {QuestionnaireComponent} from './questionnaire/questionnaire.component';
import {ProfileComponent} from './profile/profile.component'; import {ProfileComponent} from './profile/profile.component';
import {SshKeysComponent} from './ssh-keys/ssh-keys.component'; import {SshKeysComponent} from './ssh-keys/ssh-keys.component';
import {AccountComponent} from './account/account.component'; import {AccountComponent} from './account/account.component';
import {CredentialsComponent} from './credentials/credentials.component';
//providers //providers
import {DialogService} from './dialog.service'; import {DialogService} from './dialog.service';
...@@ -27,12 +28,14 @@ import {DialogService} from './dialog.service'; ...@@ -27,12 +28,14 @@ import {DialogService} from './dialog.service';
ProfileComponent, ProfileComponent,
SshKeysComponent, SshKeysComponent,
AccountComponent, AccountComponent,
CredentialsComponent,
], ],
entryComponents: [ entryComponents: [
QuestionnaireComponent, QuestionnaireComponent,
ProfileComponent, ProfileComponent,
SshKeysComponent, SshKeysComponent,
AccountComponent, AccountComponent,
CredentialsComponent,
], ],
providers: [ providers: [
DialogService, DialogService,
......
<div class="mat-typography"> <div class="mat-typography">
<h2>Data questionnaire</h2> <h2>Data questionnaire</h2>
<p> <p>
The site {{taskItem.site.name}} needs the following data to give you access to the service {{ The site {{stateItem.site.name}} needs the following data to give you access to the service {{
taskItem.service.name}}. stateItem.service.name}}.
</p> </p>
<form class="form-vertical" (ngSubmit)="sendAnswers()" #questionnaireForm="ngForm"> <form class="form-vertical" (ngSubmit)="sendAnswers()" #questionnaireForm="ngForm">
<mat-form-field *ngFor="let key of taskItem.questionnaire | ObjKeys"> <mat-form-field *ngFor="let key of stateItem.questionnaire | ObjKeys">
<input matInput required <input matInput required
placeholder="{{taskItem.questionnaire[key]}}" [(ngModel)]="answers[key]" name="key"> placeholder="{{stateItem.questionnaire[key]}}" [(ngModel)]="answers[key]" name="key">
</mat-form-field> </mat-form-field>
<button mat-raised-button color="primary" type="submit" [disabled]="!questionnaireForm.form.valid">Submit</button> <button mat-raised-button color="primary" type="submit" [disabled]="!questionnaireForm.form.valid">Submit</button>
</form> </form>
......
...@@ -2,6 +2,7 @@ import {Component, OnInit,Inject} from '@angular/core'; ...@@ -2,6 +2,7 @@ import {Component, OnInit,Inject} from '@angular/core';
import {MAT_DIALOG_DATA} from '@angular/material'; import {MAT_DIALOG_DATA} from '@angular/material';
import { UserService } from '../../user.service'; import { UserService } from '../../user.service';
import * as t from '../../types/types.module';
@Component({ @Component({
selector: 'app-questionnaire', selector: 'app-questionnaire',
...@@ -10,13 +11,13 @@ import { UserService } from '../../user.service'; ...@@ -10,13 +11,13 @@ import { UserService } from '../../user.service';
}) })
export class QuestionnaireComponent implements OnInit { export class QuestionnaireComponent implements OnInit {
public answers: Object = {}; public answers: Object = {};
public taskItem: any; public stateItem: t.DeploymentStateItem;
constructor( constructor(
public userService: UserService, public userService: UserService,
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: any,
) { ) {
this.taskItem = data.taskItem; this.stateItem = data.stateItem;
} }
ngOnInit() { ngOnInit() {
...@@ -24,7 +25,7 @@ export class QuestionnaireComponent implements OnInit { ...@@ -24,7 +25,7 @@ export class QuestionnaireComponent implements OnInit {
sendAnswers() { sendAnswers() {
this.userService.sentQuestionnaire( this.userService.sentQuestionnaire(
this.taskItem.id, this.stateItem.id,
this.answers, this.answers,
); );
} }
......
...@@ -38,10 +38,10 @@ ...@@ -38,10 +38,10 @@
<ng-template #not_uploading> <ng-template #not_uploading>
<form (ngSubmit)="uploadKey()" #sshKeyForm="ngForm"> <form (ngSubmit)="uploadKey()" #sshKeyForm="ngForm">
<mat-form-field> <mat-form-field>
<input matInput placeholder="Name" required [(ngModel)]="newKeyName" name="name"> <input matInput placeholder="Name" required [(ngModel)]="formKey.name" name="name">
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<textarea matInput placeholder="Key" required [(ngModel)]="newKeyKey" name="key"></textarea> <textarea matInput placeholder="Key" required [(ngModel)]="formKey.key" name="key"></textarea>
</mat-form-field> </mat-form-field>
<button mat-raised-button color="primary" type="submit" [disabled]="!sshKeyForm.form.valid">Submit</button> <button mat-raised-button color="primary" type="submit" [disabled]="!sshKeyForm.form.valid">Submit</button>
</form> </form>
......
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material'; import { MatTableDataSource } from '@angular/material';
import {HttpClient} from '@angular/common/http';
import { UserService } from '../../user.service'; import { UserService } from '../../user.service';
import { SSHKey, NewSSHKey } from '../../types/types.module'; import { SSHKey, NewSSHKey } from '../../types/types.module';
import * as t from '../../types/types.module';
@Component({ @Component({
selector: 'app-ssh-keys', selector: 'app-ssh-keys',
templateUrl: './ssh-keys.component.html', templateUrl: './ssh-keys.component.html',
...@@ -11,32 +14,74 @@ import { SSHKey, NewSSHKey } from '../../types/types.module'; ...@@ -11,32 +14,74 @@ import { SSHKey, NewSSHKey } from '../../types/types.module';
}) })
export class SshKeysComponent implements OnInit { export class SshKeysComponent implements OnInit {
columns = ['name', 'key', 'action']; columns = ['name', 'key', 'action'];
// does the user want to upload a key? // does the user want to upload a key?
upload: boolean; upload: boolean;
newKeyName: string; formKey: t.NewSSHKey;
newKeyKey: string;
constructor( constructor(
public userService: UserService public userService: UserService,
) { } public http: HttpClient,
) {
}
ngOnInit() { ngOnInit() {
this.upload = false; this.upload = false;
this.newKeyName = ''; this.formKey = {
this.newKeyKey = ''; name: '',
key: '',
}
} }
public uploadKey() { private uploadSshKey(key: t.NewSSHKey) {
const newKey: NewSSHKey = { const body = {
name: this.newKeyName, 'type': 'add',
key: this.newKeyKey, 'key': key,
}; };
this.userService.uploadSshKey(newKey); return this.http.post('/backend/api/sshkey', body).subscribe(
(newKey: t.SSHKey) => {
if (newKey) {
this.userService.user.ssh_keys.push(newKey);
}
},
(err) => {
console.log(err);
this.userService.update();
}
);
}
private removeSshKey(key: t.SSHKey) {
return this.http.post('/backend/api/sshkey', {
'type': 'remove',
'id': key.id,
}).subscribe(
(data: {deleted: boolean}) => {
if (data && data.deleted) {
this.userService.user.ssh_keys = this.userService.user.ssh_keys.filter(
k => key.id != k.id
);
}
},
(err) => {
console.log(err);
this.userService.update();
}
);
}
public _sshKeyTable(): MatTableDataSource<any> {
return new MatTableDataSource(this.userService.user.ssh_keys);
}
public uploadKey() {
this.uploadSshKey(this.formKey);
this.ngOnInit(); this.ngOnInit();
} }
public deleteKey(key: SSHKey) { public deleteKey(key: SSHKey) {
this.userService.removeSshKey(key); this.removeSshKey(key);
this.ngOnInit(); this.ngOnInit();
} }
} }
......
<div *ngIf="userService.loggedIn()"> <div *ngIf="userService.loggedIn()">
<div style="padding-bottom: 30px;"> <div style="padding-bottom: 30px;">
<h2>Services</h2> <h2>Services</h2>
<mat-accordion *ngIf="userService.deploymentIDs.length > 0"> <mat-accordion *ngIf="userService.services.length > 0">
<app-service *ngFor="let dID of userService.deploymentIDs" [deployment]="userService.getDeployment(dID)"></app-service> <app-service *ngFor="let service of userService.services" [deployment]="userService.getDeployment(service)" [service]="service"></app-service>
</mat-accordion> </mat-accordion>
<p *ngIf="userService.deploymentIDs.length === 0"> <p *ngIf="userService.services.length === 0">
You have no available services.<br/> You have no available services.<br/>
This is due services requiring users to be member of a certain group. This is due services requiring users to be member of a certain group.
</p> </p>
......
<mat-expansion-panel *ngIf="deployment"> <mat-expansion-panel *ngIf="service">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title>{{ service.name }}</mat-panel-title> <mat-panel-title>{{ service.name }}</mat-panel-title>
<mat-panel-description>{{ service.description }}</mat-panel-description> <mat-panel-description>{{ service.description }}</mat-panel-description>
...@@ -16,11 +16,12 @@ ...@@ -16,11 +16,12 @@
[ngSwitch]="taskState(site, key)" class="childs-inline"> [ngSwitch]="taskState(site, key)" class="childs-inline">
<!-- states with actions --> <!-- states with actions -->
<button *ngSwitchCase="'deployed'" mat-button mat-icon-button <button *ngSwitchCase="'deployed'" mat-button mat-icon-button
(click)="dialog.openCredentials(taskItem(site, key))"
matTooltip="The key {{key.name}} is deployed to the site. Click to see details."> matTooltip="The key {{key.name}} is deployed to the site. Click to see details.">
<mat-icon style="vertical-align: middle">call_made</mat-icon> <mat-icon style="vertical-align: middle">call_made</mat-icon>
</button> </button>
<button *ngSwitchCase="'questionnaire'" mat-button mat-icon-button <button *ngSwitchCase="'questionnaire'" mat-button mat-icon-button
(click)="dialog.openQuestionnaire(userService.taskItem(site, service))" (click)="dialog.openQuestionnaire(taskItem(site, y))"
matTooltip="This site needs more data to deploy the keys. Please click to submit the data."> matTooltip="This site needs more data to deploy the keys. Please click to submit the data.">
<mat-icon>warning</mat-icon> <mat-icon>warning</mat-icon>
</button> </button>
...@@ -31,18 +32,18 @@ ...@@ -31,18 +32,18 @@
<span *ngSwitchCase="'removal_pending'" mat-icon-button matTooltip="Waiting for the removal of the key {{ key.name }} from the site"> <span *ngSwitchCase="'removal_pending'" mat-icon-button matTooltip="Waiting for the removal of the key {{ key.name }} from the site">
<mat-progress-spinner diameter="24" mode="indeterminate"></mat-progress-spinner> <mat-progress-spinner diameter="24" mode="indeterminate"></mat-progress-spinner>
</span> </span>
<span *ngSwitchCase="'not_deployed'" mat-icon-button matTooltip="The key {{ key.name }} is not deployed to this site."> <button *ngSwitchCase="'not_deployed'" mat-icon-button matTooltip="The key {{ key.name }} is not deployed to this site.">
<mat-icon style="vertical-align: middle">call_received</mat-icon> <mat-icon style="vertical-align: middle">call_received</mat-icon>
</span> </button>
<span *ngSwitchCase="'failed'" mat-icon-button matTooltip="This site failed to deploy the credentials. The deployment will be retried."> <button *ngSwitchCase="'failed'" mat-icon-button matTooltip="This site failed to deploy the credentials. The deployment will be retried.">
<mat-icon style="vertical-align: middle">error</mat-icon> <mat-icon style="vertical-align: middle">error</mat-icon>
</span> </button>
<span *ngSwitchCase="'rejected'" mat-icon-button matTooltip="This site rejected the deployment of the key {{ key.name }}."> <button *ngSwitchCase="'rejected'" mat-icon-button matTooltip="This site rejected the deployment of the key {{ key.name }}.">
<mat-icon style="vertical-align: middle">error</mat-icon> <mat-icon style="vertical-align: middle">error</mat-icon>
</span> </button>
<!-- hacky solution --> <!-- if we have no deployment -> assume state == not_deployed -->
<span *ngSwitchDefault mat-icon-button matTooltip="Receiving update from backend"> <span *ngSwitchDefault mat-icon-button matTooltip="The key {{ key.name }} is not deployed to this site.">
<mat-progress-spinner diameter="24" mode="indeterminate"></mat-progress-spinner> <mat-icon style="vertical-align: middle">call_received</mat-icon>
</span> </span>
</span> </span>
</span> </span>
...@@ -50,11 +51,11 @@ ...@@ -50,11 +51,11 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="padding-right: 35px;" <td *ngFor="let group of service.groups" style="margin-right: 15px;">
*ngFor="let group of service.groups" <span matTooltip="Can be used with membership of group {{ group.name }}">
matTooltip="Can be used with membership of group {{ group.name }}"> <mat-icon style="vertical-align: middle; padding-right: 5px;">lock outline</mat-icon>
<mat-icon style="vertical-align: middle; padding-right: 5px;">lock outline</mat-icon> {{ group.name }}
{{ group.name }} </span>
</td> </td>
</tr> </tr>
</table> </table>
......
...@@ -14,8 +14,8 @@ import * as t from '../types/types.module'; ...@@ -14,8 +14,8 @@ import * as t from '../types/types.module';
styleUrls: ['./service.component.css'] styleUrls: ['./service.component.css']
}) })
export class ServiceComponent implements OnInit { export class ServiceComponent implements OnInit {
@Input() service: t.Service;
@Input() deployment: t.Deployment; @Input() deployment: t.Deployment;
public service: t.Service;
constructor( constructor(
public userService: UserService, public userService: UserService,
...@@ -25,7 +25,6 @@ export class ServiceComponent implements OnInit { ...@@ -25,7 +25,6 @@ export class ServiceComponent implements OnInit {
} }
ngOnInit() { ngOnInit() {
this.service = this.deployment.service;
} }
public isDeployed(key: t.SSHKeyRef): boolean { public isDeployed(key: t.SSHKeyRef): boolean {
......
...@@ -51,12 +51,16 @@ export interface Service { ...@@ -51,12 +51,16 @@ export interface Service {
group: Group[]; group: Group[];
} }
interface JSONObject {
[key: string]: string;
}
export interface DeploymentStateItem { export interface DeploymentStateItem {
id: number; id: number;
site: Site; site: Site;
state: string; state: string;
questionnaire: any; questionnaire: JSONObject;
credentials: any; credentials: JSONObject;
key: SSHKeyRef; key: SSHKeyRef;
service: Service; service: Service;
} }
......
...@@ -24,8 +24,8 @@ export class UserService { ...@@ -24,8 +24,8 @@ export class UserService {
public userState: t.UserState; public userState: t.UserState;
public messages: string[] = []; public messages: string[] = [];
// local copy of deployments // local copy of services
public deploymentIDs: number[] = []; public services: t.Service[] = [];
constructor( constructor(
...@@ -94,14 +94,24 @@ export class UserService { ...@@ -94,14 +94,24 @@ export class UserService {
this.user = newUser; this.user = newUser;
} }
private updateDeployments(newDeployments: t.Deployment[]){ private updateServices(newServices: t.Service[]){
if (this.deploymentIDs.length !== newDeployments.length) { if (this.services.length !== newServices.length) {
for (let dep of newDeployments) { this.services = [];
this.deploymentIDs.push(dep.id); for (let service of newServices) {
this.services.push(service);
} }
} }
} }
private _logout() {
this._loggedIn = false;
this.snackBar.open('Logged out');
this._stompService.disconnect();
this.services = [];
this.user = null;
this.userState = null;
}
private updateUserState(newState: t.UserState) { private updateUserState(newState: t.UserState) {
// did a login occur? // did a login occur?
let login = (!this._loggedIn && newState); let login = (!this._loggedIn && newState);
...@@ -112,26 +122,30 @@ export class UserService { ...@@ -112,26 +122,30 @@ export class UserService {
if (login) { if (login) {
this._loggedIn = true; this._loggedIn = true;
this.snackBar.open('Logged in'); this.snackBar.open('Logged in');
this.connectLiveUpdates() this.connectLiveUpdates()
} }
if (logout) { if (logout) {
this._loggedIn = false; this._logout()
this.snackBar.open('Logged out'); return
this._stompService.disconnect();
} }
this.updateDeployments(newState.deployments);
this.userState = newState; this.userState = newState;
} }
private stateAPIUpdate(update: t.StateAPIResult) { private stateAPIUpdate(update: t.StateAPIResult) {
this.updateUser(update.user) if (update) {
this.updateUserState(update.user_state); this.updateUser(update.user)
this.updateUserState(update.user_state);
// report an occured error this.updateServices(update.services);
if (update.error) {
this.snackBar.open(update.error); // report an occured error
if (update.error) {
this.snackBar.open(update.error);
}
} else {
console.log("Got null update");
} }
} }
...@@ -140,10 +154,10 @@ export class UserService { ...@@ -140,10 +154,10 @@ export class UserService {
} }
// PUBLIC API // PUBLIC API
public getDeployment(id: number): t.Deployment { public getDeployment(service: t.Service): t.Deployment {
return this.userState.deployments.find( return this.userState.deployments.find(
dep => { dep => {
return dep.id == id return dep.service.id === service.id;
} }
); );
} }
...@@ -225,49 +239,16 @@ export class UserService { ...@@ -225,49 +239,16 @@ export class UserService {
this.http.post('/backend/auth/v1/logout', {}).subscribe( this.http.post('/backend/auth/v1/logout', {}).subscribe(
(data: t.StateAPIResult) => { (data: t.StateAPIResult) => {
this.stateAPIUpdate(data); this.stateAPIUpdate(data);
}
);
}
public uploadSshKey(key: t.NewSSHKey) {
const body = {
'type': 'add',
'key': key,
};
return this.http.post('/backend/api/sshkey', body).subscribe(
(data: t.UserState) => {
this.updateUserState(data);
}, },
(err) => { (err) => {
this.snackBar.open('Error uploading key');
console.log(err); console.log(err);
this.update(); this._logout();
}
);
}
public removeSshKey(key: t.SSHKey) {
const body = {
'type': 'remove',
'id': key.id,
};
return this.http.post('/backend/api/sshkey', body).subscribe(
(data: t.UserState) => {
this.updateUserState(data);
this.user.ssh_keys = this.user.ssh_keys.filter(
k => key.id == k.id
);
},
(err) => {
this.snackBar.open('Error deleting key');
console.log(err);
this.update();
} }
); );