Commit 6552be3b authored by lukas.burgey's avatar lukas.burgey

Merge branch 'dev'

parents 17278ec6 9d8cd20f
DIST := dist
HOME := /home/feudal
DEST := $(HOME)/www
NM := node_modules
NG := $(NM)/.bin/ng
.PHONY: clean, dev, prod, $(NM)
NG_BUILD := $(NG) build --aot --output-path $(DEST)
.PHONY: all, clean, cleaner, dev, watch, prod, watch-prod, $(NM)
all: prod
......@@ -18,16 +21,16 @@ $(NM):
$(NG): $(NM)
dev: $(NG)
@$(NG) build --aot
dev: $(HOME) $(NG)
$(NG_BUILD)
watch: $(NG)
@$(NG) build --aot --watch
watch: $(HOME) $(NG)
$(NG_BUILD) --watch
prod: $(NG)
@$(NG) build --aot --prod
prod: $(HOME) $(NG)
$(NG_BUILD) --prod
watch-prod: $(NG)
@$(NG) build --aot --prod --watch
watch-prod: $(HOME) $(NG)
$(NG_BUILD) --prod --watch
$(DIST): prod
$(DEST): $(HOME) prod
......@@ -92,31 +92,6 @@
}
}
}
},
"feudal-e2e": {
"root": "",
"sourceRoot": "",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "./protractor.conf.js",
"devServerTarget": "feudal:serve"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"e2e/tsconfig.e2e.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "feudal",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
<header feudal-header></header>
<div class="body">
<div *ngIf="(userService.combiSrc() | async) as combi" style="max-width: 800px;" class="centered">
<div *ngIf="(userService.combiSrc() | async) as combi" class="centered">
<div *ngIf="combi.user != undefined">
<div *ngIf="(userService.sshKeysSrc() | async)?.length > 0; else noCredentials">
<div style="margin-bottom: 50px;">
......
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title style="min-width: 100px;">{{ service.name }}</mat-panel-title>
<mat-panel-description *ngIf="(deployment$ | async) as dep">
<span *ngIf="dep.state_target == 'deployed'">
Access requested
</span>
</mat-panel-description>
</mat-expansion-panel-header>
<mat-expansion-panel-header>
<mat-panel-title style="min-width: 100px;">{{ service.name }}</mat-panel-title>
<mat-panel-description *ngIf="(deployment$ | async) as dep">
<span *ngIf="dep.state_target == 'deployed'">
Access requested
</span>
</mat-panel-description>
</mat-expansion-panel-header>
<!-- expansion body -->
<div>
<table>
<thead>
<tr>
<td>Site</td>
<td>Service</td>
<td>State</td>
</tr>
</thead>
<tbody>
<tr deployment-state-tr [service]="service"></tr>
</tbody>
</table>
</div>
<ng-template #noDeployment>
<p>
Your credentials were never deployed to the service {{ service.name }}
</p>
</ng-template>
<mat-action-row deployment-action-row [deployment$]="deployment$" [service]="service"></mat-action-row>
<!-- expansion body -->
<div>
<table>
<thead>
<tr>
<td>Site</td>
<td>Service</td>
<td>State</td>
<td></td>
<td>Contact</td>
</tr>
</thead>
<tbody>
<tr deployment-state-tr [service]="service"></tr>
</tbody>
</table>
</div>
<ng-template #noDeployment>
<p>
Your credentials were never deployed to the service {{ service.name }}
</p>
</ng-template>
<mat-action-row deployment-action-row [deployment$]="deployment$" [service]="service"></mat-action-row>
</mat-expansion-panel>
......@@ -2,3 +2,30 @@
.my-tooltip {
white-space: pre-line;
}
h4 {
margin-bottom: 5px;
}
td {
vertical-align: top;
}
.mat-icon {
font-size: 15px;
height: 15px;
width: 15px;
vertical-align: initial;
}
small {
font-size: small;
}
.width-limit {
max-width: 250px;
}
.bold {
font-weight: bold;
}
<!-- site column -->
<td>
<span matTooltip="{{ service.site.description}}\nThis site provides the service {{ service.name }} for you." matTooltipClass="my-tooltip">
{{ service.site.name }}
<mat-icon>help</mat-icon>
</span>
<h4>{{ service.site.name }}</h4>
<br>
<small>{{ service.site.description }}</small>
</td>
<!-- service column -->
<td>
<span matTooltip="{{ service.description }}">
{{ service.name }}
<mat-icon>help</mat-icon>
</span>
<h4 style="font-weight: bold;">{{ service.name }}</h4>
<br>
<small>{{ service.description }}</small>
</td>
<ng-container *ngIf="(state$ | async) as state; else noStateItem">
<td [matTooltip]="tooltip(state)">
<!-- state column -->
<td class="width-limit">
<span [ngSwitch]="state.state" class="spaced">
<mat-icon *ngSwitchCase="'deployed'">call_made</mat-icon>
<mat-icon *ngSwitchCase="'questionnaire'">warning</mat-icon>
<mat-progress-spinner *ngSwitchCase="'deployment_pending'" diameter="24" mode="indeterminate"></mat-progress-spinner>
<mat-progress-spinner *ngSwitchCase="'removal_pending'" diameter="24" mode="indeterminate"></mat-progress-spinner>
<mat-icon *ngSwitchCase="'not_deployed'" mat-icon-button>call_received</mat-icon>
<mat-icon *ngSwitchCase="'failed'" mat-icon-button>error</mat-icon>
<mat-icon *ngSwitchCase="'rejected'" mat-icon-button>error</mat-icon>
<mat-icon *ngSwitchDefault mat-icon-button>call_received</mat-icon>
<mat-progress-spinner *ngSwitchCase="'deployment_pending'" diameter="15" mode="indeterminate"></mat-progress-spinner>
<mat-progress-spinner *ngSwitchCase="'removal_pending'" diameter="15" mode="indeterminate"></mat-progress-spinner>
<mat-icon *ngSwitchCase="'not_deployed'">call_received</mat-icon>
<mat-icon *ngSwitchCase="'failed'">error</mat-icon>
<mat-icon *ngSwitchCase="'rejected'">error</mat-icon>
<mat-icon *ngSwitchDefault>call_received</mat-icon>
</span>
{{ lang.printState(state.state) }}
<h4>{{ lang.printState(state.state) }}</h4>
<br>
<div class="mat-small">{{ stateDescription }}</div>
</td>
<!-- state dependent buttons -->
<td *ngIf="state.state == 'deployed'">
<button (click)="dialog.openCredentials(state$)" mat-raised-button class="mat-elevation-z6">
Credentials
</button>
</td>
<td *ngIf="state.state == 'questionnaire'">
<button (click)="dialog.openQuestionnaire(state$)" mat-raised-button class="mat-elevation-z6">
Questionnaire
</button>
</td>
<td *ngIf="state.state == 'failed'">
<button (click)="dialog.openMessage(state)" mat-raised-button class="mat-elevation-z6">
Failure
</button>
</td>
<td *ngIf="state.state == 'rejected'">
<button (click)="dialog.openMessage(state)" mat-raised-button class="mat-elevation-z6">
Rejected
</button>
</td>
<td *ngIf="!state.is_pending && state.is_credential_pending">
<span>
<mat-progress-spinner diameter="24" mode="indeterminate"></mat-progress-spinner>
SSH Keys pending
</span>
</td>
<td *ngIf="(state.questionnaire | ObjKeys).length > 0 && state.state == 'deployed'"
matTooltip="Change previously submitted answers">
<button (click)="dialog.openQuestionnaire(state$)" mat-icon-button>
<mat-icon>edit</mat-icon>
</button>
<!-- actions column -->
<td>
<div *ngIf="button !== undefined">
<button (click)="buttonAction(state)" matTooltip="{{ button.tooltip }}" color="{{ button.color }}" mat-raised-button class="mat-elevation-z6">
{{ button.text }}
</button>
</div>
<div *ngIf="!state.is_pending && state.is_credential_pending">
<span>
<mat-progress-spinner diameter="15" mode="indeterminate"></mat-progress-spinner>
SSH Keys pending
</span>
</div>
<div *ngIf="(state.questionnaire | ObjKeys).length > 0 && state.state == 'deployed'"
matTooltip="Change previously submitted answers">
<button (click)="dialog.openQuestionnaire(state$)" mat-icon-button>
<mat-icon>edit</mat-icon>
</button>
</div>
</td>
</ng-container>
<ng-template #noStateItem>
<!-- state column -->
<td>
<span>
<mat-icon mat-icon-button matTooltip="Access to this service was never requested.">call_received</mat-icon>
<mat-icon mat-icon-button>call_received</mat-icon>
</span>
{{ lang.printState('not_deployed') }}
<h4>{{ lang.printState('not_deployed') }}</h4>
<br>
<small>Access to this service was never requested.</small>
</td>
<!-- actions column -->
<td></td>
</ng-template>
<!-- contacts column -->
<td >
<button mat-raised-button matTooltip="{{ service.contact_email }}" (click)="contactSupport()">
<small>{{ service.contact_description }}</small>
</button>
</td>
......@@ -5,7 +5,13 @@ import { Observable } from 'rxjs';
import { UserService } from '../user.service';
import { LanguageService } from '../language.service';
import { DialogService } from '../dialogues/dialog.service';
import { Site, Service, DeploymentState } from '../types/types.module';
import { Site, Service, DeploymentState, StateID } from '../types/types.module';
interface Button {
text: string;
tooltip: string;
color: string;
}
@Component({
selector: '[deployment-state-tr]',
......@@ -16,9 +22,41 @@ export class StateComponent implements OnInit {
@Input() service: Service;
buttons: Record<StateID, Button | undefined> = {
'not_deployed' : undefined,
'deployment_pending': undefined,
'removal_pending': undefined,
'deployed': {
color: 'primary',
text: 'View Credentials',
tooltip: 'View detailed information about the deployed credentials'
},
'questionnaire': {
color: 'warn',
text: 'Submit Data',
tooltip: 'Submit missing data',
},
'failed': {
color: 'warn',
text: 'Show Error',
tooltip: 'Show more details on the error',
},
'rejected': {
color: 'warn',
text: 'Show Message',
tooltip: 'Show Message',
},
};
stateDescriptions: Record<StateID, string>;
// set in subscribe
public button: Button | undefined = undefined;
public stateDescription = '';
public state$: Observable<DeploymentState>;
public stateTooltip: string;
constructor(
public userService: UserService,
......@@ -27,29 +65,53 @@ export class StateComponent implements OnInit {
) { }
ngOnInit() {
this.stateDescriptions = {
'not_deployed': `Your credentials are not deployed for ${ this.service.name }.`,
'deployment_pending': `Waiting for the deployment of your credentials to ${ this.service.site.name }.`,
'removal_pending': `Waiting for the removal of your credentials from ${ this.service.site.name }.`,
'deployed': `
The credentials are deployed for this service ${ this.service.name }.
Click "${ this.buttons['deployed'].text }" see details on your deployed ssh keys and credentials.
`,
'questionnaire': `
More data is needed to deploy your credentials.
Please click "${ this.buttons['questionnaire'].text }" to submit the missing data.
`,
'failed': `
The deployment of your credentials failed, but will be tried again.
Please click "${ this.buttons['failed'].text }" for more details concerning this error.
`,
'rejected': `
The deployment of your credentials was rejected.
Please click "${ this.buttons['rejected'].text }" to show more details.
`,
};
this.state$ = this.userService.subscribeStateFor(this.service);
this.stateTooltip = 'foo';
this.state$.subscribe(
(state: DeploymentState | undefined) => {
if (state !== undefined) {
this.button = this.buttons[state.state];
this.stateDescription = this.stateDescriptions[state.state];
}
}
);
}
public tooltip(state: DeploymentState): string {
public buttonAction(state: DeploymentState): void {
switch (state.state) {
case 'not_deployed':
return `The credentials are not deployed for the service ${ this.service.name }.`;
case 'deployed':
return `The credentials are deployed for the service ${ this.service.name }. Click to see details.`;
case 'deployment_pending':
return `Waiting for the deployment of the credentials to the site ${ this.service.site.name }`;
case 'removal_pending':
return `Waiting for the removal of the credentials from the site ${ this.service.site.name }`;
return this.dialog.openCredentials(this.state$);
case 'questionnaire':
return `Site ${ this.service.site.name } needs more data to deploy the keys. Please click to submit the data.`;
return this.dialog.openQuestionnaire(this.state$);
case 'failed':
return `Site ${ this.service.site.name } failed to deploy the credentials. The deployment will be retried. Click for details.`;
return this.dialog.openMessage(state);
case 'rejected':
return `Site ${ this.service.site.name } rejected the deployment of the credentials. Click for details.`;
default:
return 'Access to this service was never requested.';
return this.dialog.openMessage(state);
}
}
public contactSupport() {
window.location.href = `mailto:${ this.service.contact_email }?subject=%5BFEUDAL%5D%20Help%20request%0A`;
}
}
......@@ -2,6 +2,26 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
// STATE ENUMS
const states = [
'deployment_pending',
'removal_pending',
'deployed',
'not_deployed',
'questionnaire',
'failed',
'rejected',
] as const;
export type StateID = typeof states[number];
const stateTargets = [
'deployed',
'not_deployed',
] as const;
export type StateTargetID = typeof stateTargets[number];
// auth stuff
export interface IdP {
id: number;
......@@ -57,6 +77,8 @@ export interface Service {
site: Site;
description: string;
vos: VO[];
contact_email: string;
contact_description: string;
}
export interface JSONObject {
......@@ -66,8 +88,8 @@ export interface JSONObject {
export interface CredentialState {
credential: SSHKeyRef;
is_pending: boolean;
state: string;
state_target: string;
state: StateID;
state_target: StateTargetID;
}
export interface DeploymentState {
......@@ -81,16 +103,16 @@ export interface DeploymentState {
questionnaire: JSONObject | undefined;
service: Service;
site: Site;
state: string;
state_target: string;
state: StateID;
state_target: StateTargetID;
}
export interface Deployment {
id: number;
service?: Service; // only for ServiceDeployment
services?: Service[]; // only for VODeployment
state: string;
state_target: string;
state: StateID;
state_target: StateTargetID;
states: DeploymentState[];
vo?: VO; // only for VODeployment
}
......
......@@ -221,7 +221,7 @@ export class UserService {
}
private fetch(): void {
this.http.get<State>('/backend/api/state').subscribe(
this.http.get<State>('/webpage/state').subscribe(
(state: State) => this.updateState(state),
this.handleError(false, 'Error fetching state. Try again later'),
);
......@@ -270,7 +270,7 @@ export class UserService {
}
private fetchIdPInfo(): void {
this.http.get<IdPInfo>('/backend/auth/v1/info').subscribe(
this.http.get<IdPInfo>('/auth/info').subscribe(
idpInfo => {
this.idpInfo.next(idpInfo);
this.idpInfo.complete();
......@@ -304,27 +304,31 @@ export class UserService {
public login(idp?: IdP): void {
const redirect = (arg: IdP) => {
window.location.href = '/?idphint=' + encodeURIComponent(arg.issuer_uri);
window.location.href = '/?idp=' + encodeURIComponent(arg.issuer_uri);
};
// was there an argument?
if (idp !== undefined) {
this.prefs.setPreferredIdP(idp);
redirect(idp);
} else {
this.prefs.connect().subscribe(
prefs => {
if (prefs.preferredIdP !== undefined) {
redirect(prefs.preferredIdP);
}
}
);
return;
}
// is the a preference?
this.prefs.connect().subscribe(
prefs => {
if (prefs.preferredIdP !== undefined) {
redirect(prefs.preferredIdP);
return;
}
}
);
console.log('Unable to login: No IdP');
}
public logout(): void {
this.http.post<State>('/backend/auth/v1/logout', undefined).subscribe(
this.http.post<State>('/auth/logout', undefined).subscribe(
(state: State) => this.updateState(state),
(err: HttpErrorResponse) => {
this.updateState(undefined);
......@@ -333,7 +337,7 @@ export class UserService {
}
public deleteUser() {
return this.http.delete('/rest/user').pipe(
return this.http.delete('/user/user').pipe(
catchError(this.handleError(true, 'Error deleting user')),
).subscribe(
_ => {
......@@ -346,7 +350,7 @@ export class UserService {
public uploadSshKey(name: string, key: string | ArrayBuffer): void {
this.http.post<SSHKey>(
'/rest/ssh-keys',
'/user/ssh-keys',
{'name': name, 'key': key},
).pipe(
catchError(this.handleError(true, 'Error changing deployment')),
......@@ -362,7 +366,7 @@ export class UserService {
public removeSshKey(key: SSHKey) {
console.log('Deleting key:', key);
return this.http.delete(
`/rest/ssh-key/${ key.id.toString() }`,
`/user/ssh-key/${ key.id.toString() }`,
).pipe(
catchError(this.handleError(true, 'Error changing deployment')),
).subscribe(
......@@ -376,7 +380,7 @@ export class UserService {
public patchDeployment(depType: string, stateTarget: string, id: number): void {
this.http.patch<Deployment>(
`/rest/deployment/${ depType }/${ id.toString() }`,
`/user/deployment/${ depType }/${ id.toString() }`,
{'state_target': stateTarget},
).pipe(
catchError(this.handleError(true, 'Error changing deployment')),
......@@ -387,7 +391,7 @@ export class UserService {
public sentQuestionnaire(stateItemID: number, answers: JSONObject) {
return this.http.patch<DeploymentState>(
`/rest/state/${ stateItemID }`,
`/user/state/${ stateItemID }`,
{'answers': answers},
).pipe(
catchError(this.handleError(true, 'Error submitting answers')),
......@@ -442,7 +446,7 @@ export class UserService {
);
}
public subscribeStateFor(service: Service): Observable<DeploymentState> {
public subscribeStateFor(service: Service): Observable<DeploymentState | undefined> {
return this.deploymentStates$.asObservable().pipe(
map((states: DeploymentState[]) => states.find(
(dsi: DeploymentState) => dsi.service.id === service.id,
......
......@@ -28,6 +28,8 @@
<td>Site</td>
<td>Service</td>
<td>State</td>
<td></td>
<td>Contact</td>
</tr>
</thead>
<tbody>
......
......@@ -748,8 +748,8 @@
<p>Records of your use of FEUDAL, collected for reasons of security (described in 4 above) will be deleted, at latest, 24 months after your last use of the service.</p>
<p>Other personal data can be deleted immediately or on request as described above.</p>
<h2 id="contact-information">Contact information</h2>
<p>Service managers: <a href="watts-prod@lists.kit.edu" class="uri">hdf-admin@lists.kit.edu</a></p>
<p>Data controller: <a href="watts-prod@lists.kit.edu" class="uri">hdf-admin@lists.kit.edu</a></p>
<p>Service managers: <a href="mailto:m-ops@lists.kit.edu" class="uri">m-ops@lists.kit.edu</a></p>
<p>Data controller: <a href="mailto:m-privacy@lists.kit.edu" class="uri">m-privacy@lists.kit.edu</a></p>
<h4 id="supervisory-authorities-dpas">Supervisory authorities (DPAs):</h4>
<ul>
<li><p><a href="https://www.bfdi.bund.de/DE/Home/home_node.html">German DPA</a>. Details for raising concerns for the German DPA can be found <a href="https://www.bfdi.bund.de/DE/Service/Datenschutzerklaerung/datenschutzerklaerung-node.html">here</a></p></li>
......
......@@ -22,12 +22,17 @@ body {
}
table {
margin: 30px 0px 30px 30px;
margin: 20px 0px 20px 20px;
width: 100%;
}
thead > tr > td {
font-size: 110%;
}
tr > * {
padding-right: 15px;
height: 42px;
padding-bottom: 15px;
}
/* same as childs inline */
......@@ -122,4 +127,6 @@ mat-panel-description > * {
.centered {
margin-left: auto;
margin-right: auto;
width: 80%;
max-width: 1200px;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!