Commit 9b29e26f authored by Chris Hines's avatar Chris Hines
Browse files

add in user info reporting

parent d3cdf312
Pipeline #7503 passed with stages
in 3 minutes and 3 seconds
...@@ -19,7 +19,7 @@ export class SshauthzServer {} ...@@ -19,7 +19,7 @@ export class SshauthzServer {}
}) })
export class AuthorisationService { export class AuthorisationService {
// public token: BehaviorSubject<AuthToken>; // public token: BehaviorSubject<AuthToken>;
public token: AuthToken public token: BehaviorSubject<AuthToken>;
public sshAuthzServers: BehaviorSubject<SshAuthzServer[]>; public sshAuthzServers: BehaviorSubject<SshAuthzServer[]>;
public loggedInAuthZ: BehaviorSubject<SshAuthzServer[]>; public loggedInAuthZ: BehaviorSubject<SshAuthzServer[]>;
public loggedOutAuthZ: BehaviorSubject<SshAuthzServer[]>; public loggedOutAuthZ: BehaviorSubject<SshAuthzServer[]>;
...@@ -38,7 +38,7 @@ export class AuthorisationService { ...@@ -38,7 +38,7 @@ export class AuthorisationService {
private router: Router, private router: Router,
private backendSelectionService: BackendSelectionService, private backendSelectionService: BackendSelectionService,
private location: Location) { private location: Location) {
// this.token = new BehaviorSubject<AuthToken>(new AuthToken('','')); this.token = new BehaviorSubject<AuthToken>(null);
this.readyToNavigate = new Subject<[Boolean,string]>(); this.readyToNavigate = new Subject<[Boolean,string]>();
this.readyToNavigate.next([false,'']); this.readyToNavigate.next([false,'']);
this.progress = new Subject<string>(); this.progress = new Subject<string>();
...@@ -156,38 +156,38 @@ export class AuthorisationService { ...@@ -156,38 +156,38 @@ export class AuthorisationService {
return return
} }
// this.token.next(new AuthToken(tokenmatch[1],tuple[0])); this.token.next(new AuthToken(tokenmatch[1],tuple[0]));
this.token = new AuthToken(tokenmatch[1],tuple[0]); //this.token = new AuthToken(tokenmatch[1],tuple[0]);
} }
public getCert() { public getCert() {
var token: AuthToken;
token = this.token.value
if (this.backendSelectionService.apiserver.value === null) { if (this.backendSelectionService.apiserver.value === null) {
this.backendSelectionService.apiserver.pipe(take(1)).subscribe(() => this.getCert()) this.backendSelectionService.apiserver.pipe(take(1)).subscribe(() => this.getCert())
} }
if (token.token === undefined || token.token === '' || token.token == null) {
this.token.pipe(take(1)).subscribe(() => this.getCert())
}
let token = this.token
if (token.token === undefined || token.token === '' || token.token == null) {
return
}
let starttime = new Date(); let starttime = new Date();
let newkeypair = keypair(); let newkeypair = keypair();
let publicKey = forge.pki.publicKeyFromPem(newkeypair.public); let publicKey = forge.pki.publicKeyFromPem(newkeypair.public);
let sshpub = forge.ssh.publicKeyToOpenSSH(publicKey, 'sv2@monash.edu'); let sshpub = forge.ssh.publicKeyToOpenSSH(publicKey, 'sv2@monash.edu');
let endtime = new Date(); let endtime = new Date();
let headers = new HttpHeaders(); let headers = new HttpHeaders();
let options = { headers: headers, withCredentials: true}; let options = { headers: headers, withCredentials: true};
let data = {'token': token.token, 'pubkey': sshpub, 'signing_url': token.sshauthzservice.sign}; let data = {'token': token.token, 'pubkey': sshpub, 'signing_url': token.sshauthzservice.sign};
this.http.post<any>(this.backendURI+'/getcert',data, options) this.http.post<any>(this.backendURI+'/getcert',data, options)
.pipe(catchError(this.handleError([]))) .pipe(catchError(this.handleError([])))
.subscribe(resp => this.makeKeyCert(newkeypair.private, resp, token.sshauthzservice), .subscribe(resp => this.makeKeyCert(newkeypair.private, resp, token.sshauthzservice),
error => this.signingError(error,token.sshauthzservice)); error => this.signingError(error,token.sshauthzservice));
} }
public getKeys(id?: Identity) { public getKeys(id?: Identity) {
......
...@@ -37,4 +37,6 @@ export class Strudelapp { ...@@ -37,4 +37,6 @@ export class Strudelapp {
export class Health { export class Health {
stat: string; stat: string;
msg: string; msg: string;
type: string;
data: any;
} }
...@@ -11,6 +11,7 @@ export class Identity { ...@@ -11,6 +11,7 @@ export class Identity {
systemalerts: BehaviorSubject<Health[]>; systemalerts: BehaviorSubject<Health[]>;
accountalerts: BehaviorSubject<Health[]>; accountalerts: BehaviorSubject<Health[]>;
joblist: BehaviorSubject<Job[]>; joblist: BehaviorSubject<Job[]>;
quotas: any[];
constructor( username: string, site: Computesite) { constructor( username: string, site: Computesite) {
this.username = username; this.username = username;
this.site = site; this.site = site;
...@@ -18,6 +19,7 @@ export class Identity { ...@@ -18,6 +19,7 @@ export class Identity {
this.systemalerts = new BehaviorSubject<Health[]>([]); this.systemalerts = new BehaviorSubject<Health[]>([]);
this.accountalerts = new BehaviorSubject<Health[]>([]); this.accountalerts = new BehaviorSubject<Health[]>([]);
this.joblist = new BehaviorSubject<Job[]>([]); this.joblist = new BehaviorSubject<Job[]>([]);
this.quotas = [];
} }
copy_skip_catalog(): Identity { copy_skip_catalog(): Identity {
...@@ -34,6 +36,7 @@ export class Identity { ...@@ -34,6 +36,7 @@ export class Identity {
id.site.dtn = this.site.dtn; id.site.dtn = this.site.dtn;
id.site.lscmd = this.site.lscmd; id.site.lscmd = this.site.lscmd;
id.site.dtnport = this.site.dtnport; id.site.dtnport = this.site.dtnport;
id.quotas = [];
return id; return id;
} }
displayName(): string { displayName(): string {
......
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { AuthorisationService } from '../authorisation.service';
import {TesService} from '../tes.service';
import { Router, NavigationStart } from '@angular/router'; import { Router, NavigationStart } from '@angular/router';
import { timer } from 'rxjs/observable/timer'; import { AuthorisationService } from '../authorisation.service';
...@@ -14,29 +12,14 @@ import { timer } from 'rxjs/observable/timer'; ...@@ -14,29 +12,14 @@ import { timer } from 'rxjs/observable/timer';
}) })
export class KeygenComponent implements OnInit { export class KeygenComponent implements OnInit {
constructor(private router: Router, private tesService: TesService, private authService: AuthorisationService) { } constructor(private router: Router, private authService: AuthorisationService) { }
//constructor(private router: Router, private authService: AuthorisationService) { }
ngOnInit() { ngOnInit() {
// The sequence is tricky here: // This component is the callback form OAuth flows.
// 1) the auth service loads, examines the fragment and stores the OAuth2 Token required to generate certificates // We hold here while the authService extract the token from the URI and generates keys
// 2) The KeyGen component becomes visible providing a Spinner // Once the keys are generated we can navigate away from this callback component
// 3) The timer(0) executes once the component is visiable causing the authService to move onto the next phase
// 4) The authservice generates ssh keys in javascript (slow) and gets them signed (also slow)
// 5) The authservice indicates its done by setting readtToNavigate => true
// 6) the router navigates to the main page (removing the spinner etc)
// ready to navigate will be set once the keys are generated
this.authService.readyToNavigate.subscribe(readyToNavigate => this.navigate(readyToNavigate)); this.authService.readyToNavigate.subscribe(readyToNavigate => this.navigate(readyToNavigate));
// use of a timer(0) means this will be executed AFTER the component is visiable (hopefully) this.authService.getCert();
timer(0).subscribe( () => this.genKeys() );
}
genKeys() {
this.authService.getCert(this.authService.token)
}
activateToken(token) {
console.log('activate token called');
} }
navigate(readyToNavigate: [Boolean,string]) { navigate(readyToNavigate: [Boolean,string]) {
......
...@@ -14,9 +14,9 @@ ...@@ -14,9 +14,9 @@
<mat-sidenav #idSideNav mode="side" opened> <mat-sidenav #idSideNav mode="side" opened>
<div fxLayout="column" style="height: 100%"> <div fxLayout="column" style="height: 100%">
<div> <div>
<mat-accordion style="width: 100%"> <mat-accordion style="width: 100%" [displayMode]="flat">
<div *ngFor="let id of (computeSitesService.appidentities | async)"> <div *ngFor="let id of (computeSitesService.appidentities | async)">
<mat-expansion-panel (opened)="selectId(id)" style="width: 100%"> <mat-expansion-panel (afterExpand)="selectId(id)" (closed)="selectId(id)" style="width: 100%">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title> <mat-panel-title>
<span fxFlex matBadge="{{ countErrors((id.systemalerts | async), (id.accountalerts | async)) }}" <span fxFlex matBadge="{{ countErrors((id.systemalerts | async), (id.accountalerts | async)) }}"
...@@ -49,46 +49,71 @@ ...@@ -49,46 +49,71 @@
</mat-sidenav> </mat-sidenav>
<mat-sidenav-content> <mat-sidenav-content>
<div fxLayout="column" style="height: 100%"> <div fxLayout="column" style="height: 100%">
<!-- the list of warning either on the computer system or the users account --> <!-- the list of warning either on the computer system or the users account -->
<div *ngIf="(identitySubject | async) !== null" > <div *ngIf="(identitySubject | async) !== null && (appSubject | async) === null" >
<mat-list> <div *ngIf="countErrors(((identitySubject | async).systemalerts | async),((identitySubject | async).accountalerts | async)) == 0; then noAlerts else displayAlerts"></div>
<div *ngFor="let h of ((identitySubject | async).systemalerts | async)"> <ng-template #noAlerts><h2> No Alerts for {{ (identitySubject | async).displayName() }}</h2></ng-template>
<mat-list-item> <ng-template #displayAlerts>
<div *ngIf="h.stat == 'error'">
<div class='health-warn'>
{{ h.msg }}
</div>
</div>
</mat-list-item>
</div>
</mat-list>
</div>
<div *ngIf="(identitySubject | async) !== null" >
<mat-list>
<div *ngFor="let h of ((identitySubject | async).accountalerts | async)">
<mat-list-item>
<div *ngIf="h.stat == 'error'">
<div class='health-warn'>
{{ h.msg }}
</div>
</div>
</mat-list-item>
</div>
</mat-list>
</div>
<!-- The dialog asking how many resources to use to run a job --> <h2>Alerts for {{ (identitySubject | async).displayName() }}</h2>
<div *ngIf="app !== null && identity !== null" > <!-- This is all the system level alerts-->
<app-launch-dialog [identity]="identitySubject | async" [appSubject]="appSubject"></app-launch-dialog> <mat-list>
</div> <div *ngFor="let h of ((identitySubject | async).systemalerts | async)">
<mat-list-item>
<div *ngIf="h.stat == 'error'">
<div class='health-warn'>
{{ h.msg }}
</div>
</div>
</mat-list-item>
</div>
</mat-list>
<!-- this is all the user level alerts that are not quota -->
<mat-list>
<div *ngFor="let h of ((identitySubject | async).accountalerts | async)">
<div *ngIf="h.type === undefined || h.type != 'quota'">
<mat-list-item>
<div [ngClass]="h.stat == 'error' || h.stat == 'warn' ? 'health-warn': 'health-ok'">
{{ h.msg }}
</div>
</mat-list-item>
</div>
</div>
</mat-list>
<!-- this table contains all the user level alerts that are quotas -->
<table mat-table [dataSource]="quotas" style="width: 100%">
<ng-container matColumnDef="resource">
<th mat-header-cell *matHeaderCellDef> Resource </th>
<td mat-cell *matCellDef="let quota"> {{quota.data.resource}} </td>
</ng-container>
<ng-container matColumnDef="usage">
<th mat-header-cell *matHeaderCellDef> Usage </th>
<td mat-cell *matCellDef="let quota"> {{humanKBytes(quota.data.usage)}} </td>
</ng-container>
<ng-container matColumnDef="quota">
<th mat-header-cell *matHeaderCellDef> Quota </th>
<td mat-cell *matCellDef="let quota"> {{humanKBytes(quota.data.quota)}} </td>
</ng-container>
<!-- the list of running jobs --> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<app-joblist [identitySubject]="identitySubject" [appSubject]="appSubject"></app-joblist> <tr mat-row *matRowDef="let row; columns: displayedColumns;" [ngClass]="quotaClass(row)"></tr>
</table>
</ng-template>
</div>
<div *ngIf="(appSubject | async) !== null && (identitySubject | async) !== null" >
<app-launch-dialog [identity]="identitySubject | async" [appSubject]="appSubject"></app-launch-dialog>
<app-joblist [identitySubject]="identitySubject" [appSubject]="appSubject"></app-joblist>
</div>
</div>
</div>
</mat-sidenav-content> </mat-sidenav-content>
</mat-sidenav-container> </mat-sidenav-container>
......
import { Component, OnInit, NgModule, Inject, Input } from '@angular/core'; import { Component, OnInit, NgModule, Inject, Input } from '@angular/core';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatDialogModule, MatDialogConfig } from '@angular/material'; import { MatTableDataSource, MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatDialogModule, MatDialogConfig } from '@angular/material';
import { Location } from '@angular/common'; import { Location } from '@angular/common';
// import { HttpClientModule, HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; // import { HttpClientModule, HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable'; import { Observable, Subject } from 'rxjs';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { fromEvent } from 'rxjs'; import { fromEvent } from 'rxjs';
import { timer } from 'rxjs/observable/timer'; import { timer } from 'rxjs/observable/timer';
import { repeat } from 'rxjs/operators'; import { repeat, takeUntil, skip } from 'rxjs/operators';
import { Router, NavigationStart } from '@angular/router'; import { Router, NavigationStart } from '@angular/router';
import {Strudelapp} from '../strudelapp'; import {Strudelapp} from '../strudelapp';
...@@ -25,6 +26,12 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject'; ...@@ -25,6 +26,12 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
export interface Quota {
resource: string;
usage: string;
quota: string;
}
...@@ -47,6 +54,9 @@ export class LauncherComponent implements OnInit { ...@@ -47,6 +54,9 @@ export class LauncherComponent implements OnInit {
private launchwindow: any; private launchwindow: any;
private launchwindowWatcher: any; private launchwindowWatcher: any;
private subscriptions: Subscription[]; private subscriptions: Subscription[];
public quotas: BehaviorSubject<any[]>;
displayedColumns: string[] = ['resource', 'usage', 'quota'];
constructor( public dialog: MatDialog, constructor( public dialog: MatDialog,
public tesService: TesService, public tesService: TesService,
...@@ -61,24 +71,48 @@ export class LauncherComponent implements OnInit { ...@@ -61,24 +71,48 @@ export class LauncherComponent implements OnInit {
this.subscriptions.push(this.computeSitesService.appidentities.subscribe(o => this.getHealth(o))); this.subscriptions.push(this.computeSitesService.appidentities.subscribe(o => this.getHealth(o)));
this.appSubject = new BehaviorSubject<Strudelapp>(null); this.appSubject = new BehaviorSubject<Strudelapp>(null);
this.identitySubject = new BehaviorSubject<Identity>(null); this.identitySubject = new BehaviorSubject<Identity>(null);
this.quotas = new BehaviorSubject<any[]>([]);
} }
countErrors(a: Health[], b: Health[]) { countErrors(a: Health[], b: Health[]) {
var count: number = 0 var count: number = 0
var h: Health; var h: Health;
for (h of a) { for (h of a) {
if (h.stat == 'error') { if (h.stat == 'error' || h.stat == 'warn') {
count++; count++;
} }
} }
for (h of b) { for (h of b) {
if (h.stat == 'error') { if (h.stat == 'error' || h.stat == 'warn') {
count++; count++;
} }
} }
return count; return count;
} }
humanKBytes(n: number) {
if (n > 1024*1024*1024) {
let v = n/1024/1024/1024;
return v.toFixed(0)+'TB';
}
if (n > 1024*1024) {
let v = n/1024/1024;
return v.toFixed(0)+'GB';
}
if (n > 1024) {
let v = n/1024;
return v.toFixed(0)+'MB';
}
return '0 MB';
}
quotaClass(quota) {
if (quota.stat == 'error' || quota.stat == 'warn') {
return 'health-warn';
} else {
return 'health-ok';
}
}
navLogin(o) { navLogin(o) {
if (o.length == 0) { if (o.length == 0) {
...@@ -110,6 +144,13 @@ export class LauncherComponent implements OnInit { ...@@ -110,6 +144,13 @@ export class LauncherComponent implements OnInit {
selectId(id: Identity) { selectId(id: Identity) {
this.identitySubject.next(id); this.identitySubject.next(id);
this.appSubject.next(null); this.appSubject.next(null);
if (id != null) {
// We will subscribe to account alerts until the id changes (in which case we will subscribe to account alerts
// on the next id
// BUT
// since identitySubject is a behaviorsubject and already has a value, we must skip(1)
id.accountalerts.pipe(takeUntil(this.identitySubject.pipe(skip(1)))).subscribe((msgs) => { this.quotas.next(msgs.filter(q => q.type =='quota')) });
}
} }
selectApp(app: Strudelapp) { selectApp(app: Strudelapp) {
......
...@@ -26,11 +26,9 @@ ...@@ -26,11 +26,9 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div style="width: 100%"> <div fxLayout="row" fxLayoutAlign="space-around center" style="width: 100%">
<button fxFlex mat-button (click)="login()">Login</button> <button mat-flat-button (click)="login()" color=primary style="width: 25%; text-align: center">Login</button>
<div fxFlex *ngIf="(authService.loggedInAuthZ | async).length > 0"> <button *ngIf="(authService.loggedInAuthZ | async).length > 0" mat-flat-button routerLink="/launch" color=warn style="width: 25%; text-align: center">Cancel</button>
<button fxFlex mat-button routerLink="/launch">Cancel</button>
</div>
</div> </div>
<div *ngIf="selected !== undefined"> <div *ngIf="selected !== undefined">
{{ selected.desc }} {{ selected.desc }}
......
...@@ -8,13 +8,16 @@ ...@@ -8,13 +8,16 @@
</mat-toolbar-row> </mat-toolbar-row>
</mat-toolbar> </mat-toolbar>
<div style="height: 10%"></div> <div style="height: 10%"></div>
<div fxLayout=row>
<div fxLayout=row style="width: 100vw">
<div fxFlex></div> <div fxFlex></div>
<div fxFlex gdAreas="header header | logout cancel " gdGap="10%" style="text-align: center"> <div fxLayout="column" style="width: 25%">
<div gdArea="header" >Really Logout?</div> <div style="text-align: center">Really Logout?</div>
<div gdArea="logout"> <button mat-flat-button (click)="logout()" color=warn >Logout</button></div> <div fxLayout="row" fxLayoutAlign="space-around center" style="width: 100%">
<div gdArea="cancel"> <button mat-flat-button routerLink="/launch" color=primary >Cancel</button></div> <button mat-flat-button (click)="logout()" color=warn style="width: 25%;">Logout</button>
</div> <button mat-flat-button routerLink="/launch" color=primary style="width: 25%;">Cancel</button>
</div>
</div>
<div fxFlex></div> <div fxFlex></div>
</div> </div>
</div> </div>
......
...@@ -280,12 +280,14 @@ addUserHealth(identity,resp) { ...@@ -280,12 +280,14 @@ addUserHealth(identity,resp) {
let ci = identity.accountalerts.value; let ci = identity.accountalerts.value;
console.log('in add User HEalth'); console.log('in add User HEalth');
for (let i of resp) { for (let i of resp) {
if (i.stat != 'ok') { let h = new Health();
let h = new Health(); h.stat = i.stat;
h.stat = 'error'; h.msg = i.message;
h.msg = i.message; if (i.type != undefined) {
ci.push(h); h.type = i.type;
h.data = i.data;
} }
ci.push(h);
} }
identity.accountalerts.next(ci); identity.accountalerts.next(ci);
console.log('id health is',identity.accountalerts.value); console.log('id health is',identity.accountalerts.value);
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
"appCatalogUri": "./assets/config/m3apps.dev.json", "appCatalogUri": "./assets/config/m3apps.dev.json",
"cancelcmd": "/usr/local/sv2/dev/sv2scancel.sh {jobid}", "cancelcmd": "/usr/local/sv2/dev/sv2scancel.sh {jobid}",
"statcmd": "/usr/local/sv2/dev/sv2stat.py", "statcmd": "/usr/local/sv2/dev/sv2stat.py",
"userhealth": "/usr/local/sv2/dev/userhealth/bin/jsonuserhealth", "userhealth": "/home/chines/userhealth/uijson.py",
"cacheturis": ["https://cachet-dev.erc.monash.edu.au/api/v1/incidents"] "cacheturis": ["https://cachet-dev.erc.monash.edu.au/api/v1/incidents"]
}, },
{ {
......
...@@ -6,6 +6,13 @@ ...@@ -6,6 +6,13 @@
.health-warn { .health-warn {
color: mat-color($warn); color: mat-color($warn);
text-color: mat-color($warn);
}
.health-warn .mat-cell {
color: mat-color($warn);
}
.health-ok .mat-cell {
color: mat-color($foreground);
} }
.mat-menu-panel { .mat-menu-panel {
.mat-menu-item { .mat-menu-item {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment