Commit 4de33b31 authored by Chris Hines's avatar Chris Hines
Browse files

latest build

parent 82a5b259
Pipeline #6440 failed with stages
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LauncherComponent } from './launcher/launcher.component';
import { TokenextractorComponent } from './tokenextractor/tokenextractor.component';
import { KeygenComponent } from './keygen/keygen.component';
import { ConnectingComponent } from './connecting/connecting.component';
// import { TokenextractorComponent } from './tokenextractor/tokenextractor.component';
const routes: Routes = [
{ path: '', redirectTo: 'launch', pathMatch: 'full'},
{ path: 'launch', component: LauncherComponent},
// { path: 'sshauthz_callback', component: TokenextractorComponent}
{ path: 'sshauthz_callback', component: LauncherComponent}
{ path: 'finishlaunch', component: LauncherComponent},
{ path: 'cancellaunch', component: LauncherComponent},
{ path: 'sshauthz_callback', component: KeygenComponent},
{ path: 'connecting', component: ConnectingComponent }
// { path: 'sshauthz_callback', component: LauncherComponent}
];
......
......@@ -3,3 +3,8 @@
Every toolbar row uses a flexbox row layout. */
flex: 1 1 auto;
}
.fill-horizontal-space {
/* This fills the remaining space, by using flexbox.
Every toolbar row uses a flexbox row layout. */
flex: 0 1 auto;
}
<mat-toolbar color="primary">
<mat-toolbar-row>
<span>{{title}}</span>
<span class="fill-remaining-space"></span>
<span class="fill-remaining-space"></span>
<!-- <app-siteselection></app-siteselection> -->
<!-- <button mat-button *ngIf="authorised" (click)="logout()">Logout</button>
<button mat-button *ngIf="!authorised" (click)="login()">Login</button> -->
</mat-toolbar-row>
</mat-toolbar>
<router-outlet></router-outlet>
<app-teserrors></app-teserrors>
<router-outlet class="fill-remaining-space"></router-outlet>
<!-- <app-teserrors></app-teserrors> -->
......@@ -15,20 +15,31 @@ import { MatTableModule } from '@angular/material/table';
import { MatCardModule } from '@angular/material';
import { MatToolbarModule } from '@angular/material';
import { MatDialogModule, MatDialog } from '@angular/material';
import { MatSnackBarModule } from "@angular/material";
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ComputesitesService } from './computesites.service';
import { StrudelappsService } from './strudelapps.service';
import { AuthorisationService } from './authorisation.service';
import { TesService} from './tes.service';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { JobComponent } from './job/job.component';
import { SiteselectionComponent } from './siteselection/siteselection.component';
// import { SiteselectionComponent } from './siteselection/siteselection.component';
import { TeserrorsComponent } from './teserrors/teserrors.component';
import { AppRoutingModule } from './/app-routing.module';
import { TokenextractorComponent } from './tokenextractor/tokenextractor.component';
import { LogindialogComponent } from './logindialog/logindialog.component';
// import { LogindialogComponent } from './logindialog/logindialog.component';
import { LogoutdialogComponent } from './logoutdialog/logoutdialog.component';
// import { LaunchdialogComponent } from './launchdialog/launchdialog.component';
import { ModaldialogComponent } from './modaldialog/modaldialog.component';
import { MatSidenavModule, MatExpansionModule, MatIconModule } from '@angular/material';
import { StrudelapplistComponent } from './strudelapplist/strudelapplist.component';
import { KeygenComponent } from './keygen/keygen.component';
import { ConnectingComponent } from './connecting/connecting.component';
......@@ -38,11 +49,15 @@ import { LogoutdialogComponent } from './logoutdialog/logoutdialog.component';
LauncherComponent,
JoblistComponent,
JobComponent,
SiteselectionComponent,
// SiteselectionComponent,
TeserrorsComponent,
TokenextractorComponent,
LogindialogComponent,
LogoutdialogComponent
LogoutdialogComponent,
ModaldialogComponent,
// LaunchdialogComponent,
StrudelapplistComponent,
KeygenComponent,
ConnectingComponent
],
imports: [
BrowserModule,
......@@ -61,10 +76,14 @@ import { LogoutdialogComponent } from './logoutdialog/logoutdialog.component';
FormsModule,
HttpClientModule,
AppRoutingModule,
MatSidenavModule,
MatExpansionModule,
MatIconModule,
MatSnackBarModule,
],
entryComponents: [ LogindialogComponent, LogoutdialogComponent ],
providers: [ StrudelappsService, ComputesitesService, TesService, MatDialog],
entryComponents: [ ModaldialogComponent, LogoutdialogComponent, ],
providers: [ StrudelappsService, ComputesitesService, TesService, MatDialog, AuthorisationService],
bootstrap: [AppComponent]
})
export class AppModule { }
......@@ -8,16 +8,24 @@ import {LocationStrategy} from '@angular/common';
// import { keypair } from 'keypair';
import * as keypair from 'keypair';
import * as forge from "node-forge";
import { Identity, AuthToken, KeyCert, AuthService } from './identity';
import { Identity, AuthToken, KeyCert, SshAuthzServer } from './identity';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Subject} from 'rxjs/Subject';
import {TesService} from './tes.service';
import { throwError } from 'rxjs';
export class SshauthzServer {}
@Injectable({
providedIn: 'root'
})
export class AuthorisationService {
private token: Subject<AuthToken>;
// public token: BehaviorSubject<AuthToken>;
public token: AuthToken
public SshAuthzServers: BehaviorSubject<SshAuthzServer[]>;
public readyToNavigate: Subject<Boolean>;
public progress: Subject<string>;
// private keyCert: Subject<KeyCert>;
......@@ -26,10 +34,30 @@ export class AuthorisationService {
private route: ActivatedRoute,
private router: Router,
private tesService: TesService) {
this.token = new Subject<AuthToken>();
this.token.subscribe(token => this.getCert(token));
console.log('created AuthorisationService');
// this.token = new BehaviorSubject<AuthToken>(new AuthToken('',''));
this.readyToNavigate = new Subject<Boolean>();
this.readyToNavigate.next(false);
this.progress = new Subject<string>();
this.progress.next("");
// this.token.subscribe(token => this.getCert(token));
this.route.fragment.subscribe(frag => this.storeToken(frag));
this.SshAuthzServers = new BehaviorSubject<SshAuthzServer[]>([]);
this.getSshAuthzServers();
}
getSshAuthzServers() {
let headers = new HttpHeaders();
let options = { headers: headers, withCredentials: false};
this.http.get<SshAuthzServer[]>('./assets/config/authservers.json',options)
.pipe(catchError(this.handleError('getSshAuthzServers')))
.subscribe(resp => this.updateSshAuthzServers(resp));
}
updateSshAuthzServers(resp) {
this.SshAuthzServers.next(<SshAuthzServer[]>resp);
}
storeToken(frag: string) {
if (frag === undefined || frag == null) {
return;
......@@ -37,8 +65,8 @@ export class AuthorisationService {
let tokenmatch = null;
let statematch = null;
if (!(frag === undefined) && !(frag == null)) {
tokenmatch = frag.match(/access_token\=([\S\s]*?)[&|$]/);
statematch = frag.match(/state\=([\S\s]*?)[&|$]/);
tokenmatch = frag.match(/access_token\=([^&]+)(&|$)/);
statematch = frag.match(/state\=([^&]+)(&|$)/);
}
if (tokenmatch == null || statematch == null) {
return;
......@@ -46,7 +74,7 @@ export class AuthorisationService {
let accesstoken = tokenmatch[1];
let state = statematch[1];
this.router.navigate(['/']);
// this.router.navigate(['/']);
//Verify that the state matched the nonce we used when initiating login
let tuple = JSON.parse(localStorage.getItem('authservice'));
......@@ -54,14 +82,13 @@ export class AuthorisationService {
return
}
this.token.next(new AuthToken(tokenmatch[1],tuple[0]));
// TODO fire off a query to the auth service to get the associated sites
// this.token.next(new AuthToken(tokenmatch[1],tuple[0]));
this.token = new AuthToken(tokenmatch[1],tuple[0]);
}
getCert(token: AuthToken) {
public getCert(token: AuthToken) {
console.log('in getCert');
if (token.token === undefined || token.token === '' || token.token == null) {
console.log('no authtoken available, we wont be able to generate a cert');
......@@ -80,46 +107,65 @@ export class AuthorisationService {
let headers = new HttpHeaders();
let options = { headers: headers, withCredentials: true};
let data = {'token': token.token, 'pubkey': sshpub, 'signing_url': token.authservice.sign};
let data = {'token': token.token, 'pubkey': sshpub, 'signing_url': token.sshauthzservice.sign};
console.log('posting to getcert',this.tesService.Base);
this.http.post<any>(this.tesService.Base+'/getcert',data, options)
.pipe(catchError(this.handleError('getCert',[])))
.subscribe(resp => this.makeKeyCert(newkeypair.private, resp, token.authservice))
.subscribe(resp => this.makeKeyCert(newkeypair.private, resp, token.sshauthzservice),
error => this.httperrorLogout(error,token.sshauthzservice));
console.log('getcert complete');
}
makeKeyCert(key: string, resp, authservice: AuthService) {
makeKeyCert(key: string, resp, sshauthzservice: SshAuthzServer) {
let keyCert = new KeyCert()
keyCert.key = key;
keyCert.cert = resp['cert'];
keyCert.authservice = authservice;
console.log('updating keycert',keyCert);
this.tesService.keyCert.next(keyCert);
this.tesService.sshAdd(keyCert);
// this.tesService.keyCert.next(keyCert);
// As soon as the certificate has been generated, we log back out of the signing server
if (!(sshauthzservice.logout === null)) {
window.open(sshauthzservice.logout);
}
this.readyToNavigate.next(true);
}
public login() {
public login(authservice: SshAuthzServer) {
let redirect_uri = window.location.origin+this.locationStrategy.getBaseHref()+"sshauthz_callback";
let nonce="asdfzxcv";
let authservice = new AuthService();
authservice.base = "https://autht.massive.org.au/hpcid/";
authservice.authorise = authservice.base + 'oauth/authorize';
authservice.sign = authservice.base + 'api/v1/sign_key';
authservice.client_id = "86c06039-e589-4b39-9d1f-9eca431be18f";
localStorage.setItem('authservice', JSON.stringify([authservice,nonce]));
window.location.assign(authservice.authorise+"?response_type=token&redirect_uri="+redirect_uri+"&state="+nonce+"&client_id="+authservice.client_id);
if (authservice.scope == null) {
window.location.assign(authservice.authorise+"?response_type=token&redirect_uri="+redirect_uri+"&state="+nonce+"&client_id="+authservice.client_id);
} else {
window.location.assign(authservice.authorise+"?response_type=token&redirect_uri="+redirect_uri+"&state="+nonce+"&client_id="+authservice.client_id+"&scope="+authservice.scope);
}
}
private httperror(error: any) {
this.tesService.statusMsg.next(error);
console.log(error);
}
private httperrorLogout(error: any,sshauthzservice: SshAuthzServer) {
this.tesService.statusMsg.next(error);
console.log(error);
if (!(sshauthzservice.logout === null)) {
window.open(sshauthzservice.logout);
}
}
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
console.log('in handle error',operation);
console.log(error.status);
console.error(error);
return of(result as T);
if (error.status == 500) {
return throwError("The authorisation server encountered and error. Please try again in a few minutes")
}
return throwError(error.message);
// return of(result as T);
};
}
}
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
export class Computesite {
url: string; // The URL runs a web service to help construct sbatch commands
// Infact the URL should return the entire interface for
......@@ -7,7 +9,9 @@ export class Computesite {
cafingerprint: string; // Certificates contain a CA fingerprint. We use this
// to figure out which compute site a certificate is valid
// for
appCatalog: Strudelapp[];
appCatalog: BehaviorSubject<Strudelapp[]>;
appCatalogUri: string;
}
export class Strudelapp {
......
import { Injectable } from '@angular/core';
import { Computesite } from './computesite';
import { COMPUTESITES } from './mock-compute-site';
import { Computesite, Strudelapp } from './computesite';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import { HttpClientModule, HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { catchError, map, tap } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class ComputesitesService {
public computesites: BehaviorSubject<Computesite[]>;
constructor(private http: HttpClient,) {
this.computesites = new BehaviorSubject<Computesite[]>([]);
this.computesites.subscribe(computesites => this.getStrudelApps(computesites))
this.getComputeSites();
}
constructor() { }
getStrudelApps(computesites: Computesite[]) {
for (let s of computesites) {
let headers = new HttpHeaders();
let options = { headers: headers, withCredentials: false};
this.http.get<Strudelapp[]>(s.appCatalogUri,options)
.pipe(catchError(this.handleError('getStrudelApps')))
.subscribe(resp => this.updateStrudelApps(s.appCatalog,resp));
}
getComputeSites(): Computesite[] {
return COMPUTESITES;
}
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
console.error(error);
return of(result as T);
};
}
updateStrudelApps(appCatalog: BehaviorSubject<Strudelapp[]>,apps) {
appCatalog.next(<Strudelapp[]>apps);
}
getComputeSites() {
let headers = new HttpHeaders();
let options = { headers: headers, withCredentials: false};
this.http.get<Strudelapp[]>('./assets/config/computesites.json',options)
.pipe(catchError(this.handleError('getComputeSites')))
.subscribe(resp => this.updateComputeSites(resp));
}
updateComputeSites(resp) {
var computesites: Computesite[] = []
for (let cs of resp) {
let computesite = <Computesite>cs;
computesite.appCatalog = new BehaviorSubject<Strudelapp[]>([])
computesites.push(computesite);
}
this.computesites.next(computesites);
}
returnComputeSites(): Computesite[] {
return this.computesites.value;
}
}
......@@ -4,37 +4,52 @@ import {Computesite} from './computesite';
export class Identity {
username: string;
site: Computesite;
authservice: AuthService;
authservice: SshAuthzServer;
constructor( username: string, site: Computesite) {
this.username = username;
this.site = site;
}
copy_skip_catalog(): Identity {
let id = new Identity(null,null);
id.username = this.username;
id.site = new Computesite();
id.site.url = this.site.url;
id.site.host = this.site.host;
id.site.name = this.site.name;
id.site.cafingerprint = this.site.cafingerprint;
return id;
}
displayName(): string {
return this.username+'@'+this.site.name;
}
repr(): string {
return JSON.stringify([this.username,this.site.cafingerprint,this.site.host]);
return JSON.stringify([this.username,this.site.cafingerprint,this.site.host,this.site.url]);
}
}
export class AuthToken {
token: string;
authservice: AuthService;
constructor( token: string, authservice: AuthService ) {
sshauthzservice: SshAuthzServer;
constructor( token: string, sshauthzservice: SshAuthzServer ) {
this.token = token;
this.authservice = authservice;
this.sshauthzservice = sshauthzservice;
}
}
export class KeyCert {
key: string;
cert: string;
authservice: AuthService;
// authservice: SshAuthzServer;
}
export class AuthService {
export class SshAuthzServer {
base: string;
authorise: string;
sign: string;
client_id: string;
name: string;
icon: string;
scope: string;
logout: string;
}
......@@ -9,9 +9,11 @@
{{ jobdata.state }}
</td>
<td withdt="10%">
<button mat-button (click)="onCancel()">
Cancel
</button>
<div *ngIf="!nocancel">
<button mat-button (click)="onCancel()" >
Cancel
</button>
</div>
</td>
<td width="10%">
<div *ngIf="available">
......
......@@ -13,33 +13,36 @@ export class JobComponent implements OnInit {
@Input() jobdata: Job;
public available: Boolean;
private busy: Boolean;
public nocancel: Boolean;
constructor(private tesService: TesService, private strudelAppsService: StrudelappsService) {
}
ngOnInit() {
console.log('creating job component');
if (this.jobdata.state == "RUNNING") {
this.available = true;
} else {
this.available = false;
}
this.tesService.busy.subscribe(busy => this.busy = busy);
console.log('creating job component complete');
if (this.jobdata.app.startscript === null) {
this.nocancel = true;
} else {
this.nocancel = false;
}
}
onCancel() {
this.jobdata.app = this.strudelAppsService.getApp(this.jobdata.name);
// this.jobdata.app = this.strudelAppsService.getApp(this.jobdata.name);
console.log(this.jobdata);
this.tesService.cancel(this.jobdata);
}
onConnect() {
console.log('attempting connect');
// Before connecting we must resolve what type of app we are connecting to
this.jobdata.app = this.strudelAppsService.getApp(this.jobdata.name);
// this.jobdata.app = this.strudelAppsService.getApp(this.jobdata.name);
this.tesService.connect(this.jobdata);
}
......
html, body {
height: 100%;
}
.wrapper {
/* display: flex;
flex-direction: column; */
height: 100%;
}
<div *ngIf="identities.length == 0">
<!-- <div *ngIf="identities.length == 0">
<mat-card>
Click identity and login to a service to start an application.
</mat-card>
</div>
<div *ngFor="let id of identities">
<mat-card>
<mat-card-title>{{ id.displayName() }}</mat-card-title>
<div *ngIf="jobs[id.repr()] == undefined || jobs[id.repr()].length == 0 ">
No jobs running yet
<div *ngFor="let id of identities"> -->
<mat-card class="fill-remaining-space">
<div *ngIf="identity == undefined" >
Select your username an compute site on the left to see running jobs or start new jobs.
</div>
<div *ngIf="!(identity == undefined)">
<div *ngFor="let job of jobs[identity.repr()]">
<app-job [jobdata]=job></app-job>
</div>
</div>
<div *ngFor="let job of jobs[id.repr()]">
<app-job [jobdata]=job></app-job>
</div>
</mat-card>
</div>
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, Input } from '@angular/core';
import {TesService} from '../tes.service';
import { Job } from '../job';
import { Observable } from 'rxjs/Observable';
......@@ -11,21 +11,23 @@ import { Identity } from '../identity';
templateUrl: './joblist.component.html',
styleUrls: ['./joblist.component.css']
})
export class JoblistComponent implements OnInit {
@Input() identity: Identity;
public jobs: {[id: string]: Job[] } = {};
public identities: Identity[];
private displayedColumns = ['id'];
private jobsSubscription: any;
private idSubscription: any;
constructor(private tesService: TesService,) {
}
ngOnInit() {
console.log('creating joblist component');
this.jobsSubscription = this.tesService.joblist.subscribe(jobs => this.updateJobs(jobs));
this.idSubscription = this.tesService.identities.subscribe(ids => this.updateIds(ids));
console.log("joblist component complete");
// this.idSubscription = this.tesService.identities.subscribe(ids => this.updateIds(ids));
}
public ngOnDestroy(): void {
......@@ -34,18 +36,19 @@ export class JoblistComponent implements OnInit {
}
}
updateIds(identities: Identity[]) {
console.log('update identities');
this.identities = identities;
console.log('update identities complete');
}
// updateIds(identities: Identity[]) {
// this.identities = identities;
// }
updateJobs(jobs) {
console.log('updating jobs')
console.log('update jobs',jobs);