diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index cf4d65b964a020b2d606a6c67c844f60a5b78356..75ecb96f68401264ce52f616f65432b7176199ca 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -7,6 +7,7 @@ import { ShareconnectComponent } from './shareconnect/shareconnect.component'; import { JoblistComponent } from './joblist/joblist.component'; import {LoginComponent} from './login/login.component'; +import {LogoutComponent} from './logout/logout.component'; import {SettingsComponent} from './settings/settings.component'; @@ -18,6 +19,7 @@ const routes: Routes = [ //{ path: 'launch', component: JoblistComponent}, { path: 'launch', component: LauncherComponent}, { path: 'login', component: LoginComponent}, + { path: 'logout', component: LogoutComponent}, { path: 'settings', component: SettingsComponent }, // { path: 'finishlaunch', component: LauncherComponent}, //{ path: 'cancellaunch', component: LauncherComponent}, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 15ee5479ca6771fa4eba908193faab178ed637f5..32f26bec817208fa0f22d8a1ebdb160d8d8b5b35 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -25,6 +25,7 @@ import { AuthorisationService } from './authorisation.service'; import { FlexLayoutModule } from '@angular/flex-layout'; import { FileExplorerModule } from './file-explorer/file-explorer.module'; import { MatMenuModule } from '@angular/material'; +import { MatBadgeModule } from '@angular/material'; import { TesService} from './tes.service'; @@ -51,6 +52,7 @@ import { ModaldialogComponent } from './modaldialog/modaldialog.component' import {OverlayModule} from '@angular/cdk/overlay'; import { LoginComponent } from './login/login.component'; import { SettingsComponent } from './settings/settings.component'; +import { LogoutComponent } from './logout/logout.component'; // import { FileExplorerModule } from './file-explorer/file-explorer.module'; @@ -76,6 +78,7 @@ import { SettingsComponent } from './settings/settings.component'; ModaldialogComponent, LoginComponent, SettingsComponent, + LogoutComponent, ], imports: [ BrowserModule, @@ -103,10 +106,11 @@ import { SettingsComponent } from './settings/settings.component'; MatProgressBarModule, OverlayModule, MatMenuModule, + MatBadgeModule, ], - entryComponents: [ LogoutdialogComponent, LaunchDialogComponent, ModaldialogComponent], + entryComponents: [ LogoutdialogComponent, ModaldialogComponent], providers: [ StrudelappsService, ComputesitesService, TesService, SubmitAppService, MatDialog, AuthorisationService,BackendSelectionService], bootstrap: [AppComponent] }) diff --git a/src/app/authorisation.service.ts b/src/app/authorisation.service.ts index f7ddffb437bfa4c1e15e2fec100cfa9a8dcab4a9..817ebe829a2fc53193816279101fc37fac317201 100644 --- a/src/app/authorisation.service.ts +++ b/src/app/authorisation.service.ts @@ -43,7 +43,6 @@ export class AuthorisationService { private router: Router, private backendSelectionService: BackendSelectionService, private location: Location) { - console.log('created AuthorisationService'); // this.token = new BehaviorSubject<AuthToken>(new AuthToken('','')); this.readyToNavigate = new Subject<[Boolean,string]>(); this.readyToNavigate.next([false,'']); @@ -77,7 +76,6 @@ export class AuthorisationService { } removeLocalAuthZ() { localStorage.removeItem('localauthservers'); - console.log('removed local computesites'); this.getSshAuthzServers(); } @@ -97,12 +95,9 @@ export class AuthorisationService { var found: boolean; for (let s of authzServers) { found=false; - console.log('server fp',s.cafp) for (let cert of agentContents) { - console.log('cert',cert); if ('Signing CA' in cert) { for (let ca of cert['Signing CA']) { - console.log('ca',ca); if (ca == s.cafp) { loggedin.push(s) found=true; @@ -118,11 +113,6 @@ export class AuthorisationService { loggedout.push(s) } } - console.log('updating which authz servers are logged in and out'); - console.log('logged in'); - console.log(loggedin); - console.log('logged out'); - console.log(loggedout); this.loggedOutAuthZ.next(loggedout); this.loggedInAuthZ.next(loggedin); } @@ -177,32 +167,25 @@ export class AuthorisationService { } 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'); - console.log(token); return } - console.log("Generating key matter"); let starttime = new Date(); let newkeypair = keypair(); let publicKey = forge.pki.publicKeyFromPem(newkeypair.public); let sshpub = forge.ssh.publicKeyToOpenSSH(publicKey, 'sv2@monash.edu'); let endtime = new Date(); - console.log("generating new keys took", endtime.valueOf() - starttime.valueOf()) let headers = new HttpHeaders(); let options = { headers: headers, withCredentials: true}; let data = {'token': token.token, 'pubkey': sshpub, 'signing_url': token.sshauthzservice.sign}; - console.log('posting to getcert',this.backendURI); this.http.post<any>(this.backendURI+'/getcert',data, options) .pipe(catchError(this.handleError([]))) .subscribe(resp => this.makeKeyCert(newkeypair.private, resp, token.sshauthzservice), error => this.signingError(error,token.sshauthzservice)); - console.log('getcert complete'); } public getKeys(id?: Identity) { @@ -217,8 +200,6 @@ public getKeys(id?: Identity) { let keyCert = new KeyCert() keyCert.key = key; keyCert.cert = resp['cert']; - console.log('in make KeyCert',resp); - console.log('updating keycert',keyCert); var keys: KeyCert[] = []; try{ keys = JSON.parse(sessionStorage.getItem('keys')); @@ -246,20 +227,18 @@ public getKeys(id?: Identity) { this.statusMsg.next("A network error occured. Are you connected to the internet?") } this.statusMsg.next("Error querying ssh agent"); - console.log(error); } public updateAgentContents() { if (this.statusMsg !== undefined) { this.statusMsg.next("Updating the list of available accounts"); }; - console.log('querying ',this.backendURI+'/sshagent'); let headers = new HttpHeaders(); let options = { headers: headers, withCredentials: true}; var anyvar: any; this.http.get<any>(this.backendURI+'/sshagent',options) .pipe(catchError(this.handleError(anyvar))) - .subscribe(resp => { console.log('sshagent returned',resp); this.agentContents.next(resp); this.statusMsg.next("") }, + .subscribe(resp => { this.agentContents.next(resp); this.statusMsg.next("") }, error => this.querySshAgentError(error)); // .subscribe(resp => this.computeSitesService.updateIdentities(resp), // error => this.httperror(error)) @@ -285,7 +264,6 @@ public getKeys(id?: Identity) { public sshAdd(keyCert: KeyCert) { - console.log('in authorisation service sshAdd'); if (keyCert.key == undefined) { return; } @@ -305,12 +283,10 @@ public getKeys(id?: Identity) { let redirect_uri = window.location.origin+this.locationStrategy.getBaseHref()+"sshauthz_callback"; let nonce=Math.random().toString(36).substring(2, 15) - console.log(this.location.path()); localStorage.setItem('authservice', JSON.stringify([authservice,nonce])); localStorage.setItem('path', this.location.path()); - console.log('login activated, pathname stored as',window.location.pathname); if (authservice.scope == null) { window.location.assign(authservice.authorise+"?response_type=token&redirect_uri="+redirect_uri+"&state="+nonce+"&client_id="+authservice.client_id); } else { @@ -342,13 +318,11 @@ public getKeys(id?: Identity) { // } private httperror(error: any) { - console.log('authorsation service got an error'); this.statusMsg.next('There was an error logging in or generating crypto tokens'); console.error(error); } private signingError(error: any,sshauthzservice: SshAuthzServer) { this.statusMsg.next('You don\'t appear to have an account on '+sshauthzservice.name); - console.log(error); if (!(sshauthzservice.logout === null)) { window.open(sshauthzservice.logout); } @@ -359,8 +333,6 @@ public getKeys(id?: Identity) { private handleError<T> (result?: T) { return (error: any): Observable<T> => { - console.log(error) - console.log('handleError activated') if (error.status == 500) { return throwError("The backend server encountered and error. Please try again in a few minutes") } diff --git a/src/app/backend-selection.service.ts b/src/app/backend-selection.service.ts index 0433cb89d1cd1d698d55b19f00d19ee1fa98efc9..cd339314aa05d2a9bf42a497b6071c353f6155c3 100644 --- a/src/app/backend-selection.service.ts +++ b/src/app/backend-selection.service.ts @@ -22,10 +22,8 @@ export class BackendSelectionService { private initApiServer() { try { this.apiserver.next(<APIServer>JSON.parse(localStorage.getItem('apiserver'))); - console.log('apiserver set to localstorage value',this.apiserver.value); } catch { this.apiserver.next(<APIServer>environment.apiserver); - console.log('api server to set environement',this.apiserver.value); } if (this.apiserver.value == null) { this.apiserver.next(<APIServer>environment.apiserver); @@ -33,14 +31,11 @@ export class BackendSelectionService { } private saveLastApiServer(s: APIServer) { - console.log('saving api server value to local storage') localStorage.setItem('apiserver', JSON.stringify(s)); } setApiServer(server: APIServer) { - console.log('calling setAPIServer'); this.apiserver.next(server); - console.log(this.apiserver); this.saveLastApiServer(this.apiserver.value) } diff --git a/src/app/computesites.service.ts b/src/app/computesites.service.ts index 62a22380eb98b4e3a54398fe90e46999eece4f7d..c4c9d3c130942246193434e87053bfc859a34451 100644 --- a/src/app/computesites.service.ts +++ b/src/app/computesites.service.ts @@ -39,7 +39,6 @@ export class ComputesitesService { getStrudelApps(computesites: Computesite[]) { for (let s of computesites) { - console.log('retrieving apps'); let headers = new HttpHeaders(); let options = { headers: headers, withCredentials: false}; if (s.appCatalogUri !== null) { @@ -109,7 +108,6 @@ export class ComputesitesService { removeLocalComputeSites() { localStorage.removeItem('localcomputesites'); - console.log('removed local computesites'); this.getComputeSites(); } @@ -157,8 +155,6 @@ export class ComputesitesService { var identities: Identity[] = []; var appidentities: Identity[] = []; var ftidentities: Identity[] = []; - console.log('compute sites value', this.computesites.value); - console.log('certs',certs); for (cs of this.computesites.value) { for (let i in certs) { let principal = this.siteMatch(certs[i],cs); @@ -176,8 +172,6 @@ export class ComputesitesService { } this.identities.next(identities); this.ftidentities.next(ftidentities); - console.log('setting ftidentities'); - console.log(this.ftidentities.value); this.appidentities.next(appidentities); } diff --git a/src/app/identity.ts b/src/app/identity.ts index f85d6d5f5b0252e469e67131f4410199d20f69d7..4712592ae02772f5776cfc272ec23daa22bd2ce7 100644 --- a/src/app/identity.ts +++ b/src/app/identity.ts @@ -1,4 +1,6 @@ -import {Computesite} from './computesite'; +import {Computesite, Health} from './computesite'; +import {Job} from './job'; +import {BehaviorSubject} from 'rxjs'; // Identities are defined by a username on a computer, but rather than just // DNS entry, there is extra info in the Computesite export class Identity { @@ -6,15 +8,21 @@ export class Identity { site: Computesite; authservice: SshAuthzServer; keyCerts: KeyCert[]; + healthalerts: BehaviorSubject<Health[]>; + joblist: BehaviorSubject<Job[]>; constructor( username: string, site: Computesite) { this.username = username; this.site = site; this.keyCerts = []; + this.healthalerts = new BehaviorSubject<Health[]>([]); + this.joblist = new BehaviorSubject<Job[]>([]); } copy_skip_catalog(): Identity { let id = new Identity(null,null); id.username = this.username; + id.healthalerts = null; + id.joblist = null; id.site = new Computesite(); id.site.url = this.site.url; id.site.host = this.site.host; diff --git a/src/app/job/job.component.html b/src/app/job/job.component.html index 2dd24fda00632fd3dda318049632f38488be94c6..a7536627842229aa6a151e0df30129212aa6b19a 100644 --- a/src/app/job/job.component.html +++ b/src/app/job/job.component.html @@ -1,11 +1,13 @@ -<mat-card> - <mat-card-title>{{ jobdata.name }}</mat-card-title> +<mat-list-item> + <!--<mat-card-title>{{ jobdata.name }}</mat-card-title>--> + <div style="width: 100%"> <div *ngIf="jobdata.connectionState == 0"> - <div gdAreas="name name name name| + <div gdAreas="name name name name| status resources space buttons" - gdColumns="20% 60% auto 20%"> + gdColumns="20% 60% auto 20%" + > <div gdArea="name"> - {{ jobdata.desc }} + {{ jobdata.name }} {{ jobdata.desc }} </div> <div gdArea="status"> {{ jobdata.state }} @@ -36,6 +38,7 @@ </div> </div> +</div> <!-- <table> <tr> <td width="100%"> @@ -60,18 +63,31 @@ </td> </tr> </table> --> -</div> -<div *ngIf="jobdata.connectionState == 1"> - Getting app parameters - <mat-progress-bar mode="determinate" value=30></mat-progress-bar> -</div> -<div *ngIf="jobdata.connectionState == 2"> - Creating secure tunnels - <mat-progress-bar mode="determinate" value=60></mat-progress-bar> + <div *ngIf="jobdata.connectionState == 1"> + <div gdArea="status"> + Getting app parameters + </div> + <div gdArea="resources"> + <mat-progress-bar mode="determinate" value=30></mat-progress-bar> + </div> + </div> -</div> -<div *ngIf="jobdata.connectionState == 3"> - Determining correct URL - <mat-progress-bar mode="determinate" value=90></mat-progress-bar> -</div> -</mat-card> + <div *ngIf="jobdata.connectionState == 2"> + <div gdArea="status"> + Creating secure tunnels + </div> + <div gdArea="resources"> + <mat-progress-bar mode="determinate" value=60></mat-progress-bar> + </div> + </div> + + <div *ngIf="jobdata.connectionState == 3"> + <div gdArea="status"> + Determining correct URL + </div> + <div gdArea="resources"> + <mat-progress-bar mode="determinate" value=90></mat-progress-bar> + </div> + </div> + </div> +</mat-list-item> diff --git a/src/app/job/job.component.ts b/src/app/job/job.component.ts index 22363a50b0b6a6089e83e1f436eb48ffd1fe18fc..6ba50d286e7c755f5a8027886b17f90b016b2ae2 100644 --- a/src/app/job/job.component.ts +++ b/src/app/job/job.component.ts @@ -1,29 +1,35 @@ -import { Component, OnInit, Input } from '@angular/core'; +import { Component, OnInit, Input, OnDestroy } from '@angular/core'; import { Job } from '../job'; import {TesService} from '../tes.service'; import { StrudelappsService } from '../strudelapps.service'; -import { timer } from 'rxjs/observable/timer'; +import { timer, Subscription } from 'rxjs'; +import { repeat } from 'rxjs/operators'; @Component({ selector: 'app-job', templateUrl: './job.component.html', styleUrls: ['./job.component.css'] }) -export class JobComponent implements OnInit { +export class JobComponent implements OnInit, OnDestroy { @Input() jobdata: Job; public available: Boolean; public nocancel: Boolean; public resources: string; public timeremaining: string; + private updatesub: Subscription; constructor(private tesService: TesService, private strudelAppsService: StrudelappsService) { } + + ngOnDestroy() { + this.updatesub.unsubscribe(); + } ngOnInit() { - this.tesService.joblist.subscribe(() => this.updateFields()); + this.updatesub = timer(1000).pipe(repeat()).subscribe(() => this.updateFields()); } updateFields() { diff --git a/src/app/joblist/joblist.component.html b/src/app/joblist/joblist.component.html index 1229da1784522f06e86761885b3fba27780d1f7f..fabe6c5f88097fd439f3555f0b6f96b9be138101 100644 --- a/src/app/joblist/joblist.component.html +++ b/src/app/joblist/joblist.component.html @@ -1,33 +1,14 @@ -<mat-card style="width: 100%" style="box-sizing: border-box;"> - <div *ngIf="(tesService.identitySubject | async) === null" > - Click login and select a provider or select one you've already logged in to. - </div> - <div *ngIf="(tesService.identitySubject | async) !== null" > - <div *ngFor="let h of tesService.cachetincidents | async"> - <div *ngIf="h.stat == 'error'"> - <mat-card style='width: 100%'> - <div class='health-warn'> - {{ h.msg }} - </div> - </mat-card> - </div> - </div> - <div *ngFor="let h of tesService.userhealth | async"> - <div *ngIf="h.stat == 'error'"> - <mat-card style='width: 100%'> - <div class='health-warn'> - {{ h.msg }} - </div> - </mat-card> - </div> - </div> - Jobs running as {{ (tesService.identitySubject | async).displayName() }} - <div *ngFor="let job of tesService.joblist | async"> +<!--<div *ngIf="(identitySubject | async) === null" > + Click login and select a provider or select one you've already logged in to. +</div>--> +<div *ngIf="(identitySubject | async) !== null" > + <mat-list> + <div *ngFor="let job of ((identitySubject | async).joblist | async)"> + <div *ngIf="job.state != 'Finished'"> <app-job [jobdata]=job></app-job> - </div> - <button mat-button (click)="refreshJobs()" style="width: 100%; text-align: right">Refresh Job list</button> - + </div> </div> -</mat-card> + </mat-list> + <button mat-button (click)="refreshJobs()" style="width: 100%; text-align: right">Refresh Job list</button> -<!-- <iframe src="http://localhost:5200" frameborder="0" style="width: 100%"></iframe> --> +</div> diff --git a/src/app/joblist/joblist.component.ts b/src/app/joblist/joblist.component.ts index d77c229350b27e3b37f7d67ccb4b6410f96fee86..da9384fceb9c3505cc1467bcdf5675a451bbd2c5 100644 --- a/src/app/joblist/joblist.component.ts +++ b/src/app/joblist/joblist.component.ts @@ -1,12 +1,15 @@ import { Component, OnInit, Input, OnDestroy } from '@angular/core'; -import {TesService} from '../tes.service'; -import { Job } from '../job'; import { Observable } from 'rxjs/Observable'; import { delay } from 'rxjs/operators'; import { Subscription, interval } from 'rxjs'; -import { Identity } from '../identity'; import { Renderer2} from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, timer } from 'rxjs'; +import { repeat } from 'rxjs/operators'; + +import { Strudelapp } from '../strudelapp'; +import { TesService } from '../tes.service'; +import { Job } from '../job'; +import { Identity } from '../identity'; @@ -17,21 +20,31 @@ import { BehaviorSubject } from 'rxjs'; }) export class JoblistComponent implements OnInit { - @Input() identity: Identity; + @Input() identitySubject: BehaviorSubject<Identity>; + @Input() appSubject: BehaviorSubject<Strudelapp>; + private sub: Subscription; - constructor(private tesService: TesService, private renderer: Renderer2) { -} + constructor(private tesService: TesService ) { + } public ngOnInit(): void { + this.sub = timer(5000).pipe(repeat()).subscribe( () => this.getJobs() ); } - + public getJobs(): void { + if (this.identitySubject.value !== null) { + this.tesService.getJobs(this.identitySubject.value); + } + } public ngOnDestroy(): void { + if (this.sub !== undefined) { + this.sub.unsubscribe(); + } } public refreshJobs(): void { - this.tesService.getJobs(this.tesService.identitySubject.value); + this.tesService.getJobs(this.identitySubject.value); } } diff --git a/src/app/keygen/keygen.component.html b/src/app/keygen/keygen.component.html index 872fc6343930c1a70058ba865d6cbe10a54e47c5..a6af706e56e2c53cdca6b8094407a064be211b7b 100644 --- a/src/app/keygen/keygen.component.html +++ b/src/app/keygen/keygen.component.html @@ -1,3 +1,13 @@ + +<mat-toolbar color="primary"> + <mat-toolbar-row> + <button mat-icon-button ><mat-icon>menu</mat-icon></button> + + <span>Strudel v2.0</span> + <span class="fill-horizontal-space"></span> + </mat-toolbar-row> + </mat-toolbar> + <mat-card> Generating cryptographic tokens ... this should only take a few seconds </mat-card> diff --git a/src/app/launch-dialog/launch-dialog.component.html b/src/app/launch-dialog/launch-dialog.component.html index b881ef7ebf5696e9b2543b737cd449c61037c0b2..c08367aee52ae4f0e43f25aa9e05a93a927983d8 100644 --- a/src/app/launch-dialog/launch-dialog.component.html +++ b/src/app/launch-dialog/launch-dialog.component.html @@ -1,30 +1,14 @@ -<!-- -<div gdAreas="appconfig appconfig | - batchconfig batchconfig | - cancel launch" - gdRows="auto auto auto"> -<div gdArea="appconfig"> - <iframe></iframe> -</div> -<div gdArea="batchconfig" style="width: 100%"> - <iframe></iframe> -</div> -<div gdArea="launch"> - <button mat-button (click)="launch()" enabled="submitAppService.readyToLaunch | async">Launch</button> -</div> -<div gdArea="cancel"> - <button mat-button (click)="cancel()" >Cancel</button> -</div> -</div> --> -<div fxLayout="column" fxLayoutAlign="space-between stretch" style="height: 100%; width: 100%"> +<div fxLayout="column" fxLayoutAlign="space-between stretch" style="width: 100%"> <!-- <div fxFlex style="background: blue">app config</div> --> - <iframe *ngIf="appconfigurl != null" fxGrow=10 fxFlex [src]="appconfigsafeurl" style="border: none"></iframe> - <iframe *ngIf="appconfigurl != null" [src]="batchcmdsafeurl" style="height: 1px; min-height: 0px; border: none" #batchbuilderiframe></iframe> + <!--<iframe *ngIf="appconfigurl != null" fxGrow=10 fxFlex [src]="appconfigsafeurl" style="border: none"></iframe> + <iframe *ngIf="appconfigurl != null" [src]="batchcmdsafeurl" style="height: 1px; min-height: 0px; border: none" #batchbuilderiframe></iframe>--> - <iframe *ngIf="appconfigurl == null" fxFlex [src]="batchcmdsafeurl" fxGrow=1 style="border: none" #batchbuilderiframe></iframe> + <div *ngIf="(appSubject | async ) !== null" style="width: 100%"> + <iframe [src]="batchcmdsafeurl" style="border: none; border-style: none; border-width: 0px; width: 100%" [style.height]="height+'px'" #batchbuilderiframe></iframe> - <div fxLayout="row" fxLayoutAlign="space-around"> - <button mat-button (click)="cancel()">Cancel</button><button mat-button (click)="launch()" #launchbtn>Launch</button> + <div fxLayout="row" fxLayoutAlign="space-around"> + <button mat-button (click)="launch()" #launchbtn>Launch</button> + </div> </div> </div> diff --git a/src/app/launch-dialog/launch-dialog.component.ts b/src/app/launch-dialog/launch-dialog.component.ts index 5c87699160923811c06192a51d3c338cdc580e70..2fe127870f7e1f4190ae9790e4c0acfb36487203 100644 --- a/src/app/launch-dialog/launch-dialog.component.ts +++ b/src/app/launch-dialog/launch-dialog.component.ts @@ -1,9 +1,13 @@ -import { Component, OnInit, AfterViewInit, ViewChild, ElementRef, Inject } from '@angular/core'; +import { Component, OnInit, ViewChild, ElementRef, Inject, Input } from '@angular/core'; import { DomSanitizer} from '@angular/platform-browser'; import { Renderer2 } from '@angular/core'; -import { MatDialogRef,MAT_DIALOG_DATA } from '@angular/material'; -import { SubmitAppService } from '../submit-app.service'; +//import { MatDialogRef,MAT_DIALOG_DATA } from '@angular/material'; +import { TesService } from '../tes.service'; import { timer, Subscription} from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; +import { Identity } from '../identity'; +import { Strudelapp } from '../strudelapp'; +import { BatchInterface} from '../batchinterface'; @Component({ @@ -12,6 +16,9 @@ import { timer, Subscription} from 'rxjs'; styleUrls: ['./launch-dialog.component.css'] }) export class LaunchDialogComponent implements OnInit { + @Input() identity: Identity; + @Input() appSubject: BehaviorSubject<Strudelapp>; + app: Strudelapp; batchcmdurl: string; appconfigurl: string; batchcmdsafeurl: any; @@ -20,67 +27,64 @@ export class LaunchDialogComponent implements OnInit { @ViewChild('launchbtn', { read: ElementRef, static: false }) launchbtn: ElementRef; @ViewChild('batchbuilderiframe', { read: ElementRef, static: false }) batchbuilderiframe: ElementRef; setFocus: Boolean; - sub: Subscription; - - constructor( public dialogRef: MatDialogRef<LaunchDialogComponent>, - private domSanitizer: DomSanitizer, - private renderer: Renderer2, - private submitAppService: SubmitAppService) { - this.batchcmdurl = this.submitAppService.identity.value.site.url+"/"+this.submitAppService.app.value.name; - this.batchcmdsafeurl = this.domSanitizer.bypassSecurityTrustResourceUrl(this.batchcmdurl); - this.appconfigurl = this.submitAppService.app.value.url; - if (!(this.appconfigurl == null)) { - this.appconfigsafeurl = this.domSanitizer.bypassSecurityTrustResourceUrl(this.appconfigurl); - } - console.log('creating the laucnch dialog with urls',this.batchcmdsafeurl,this.appconfigsafeurl); + subscriptions: Subscription[]; + width: number; + height: number; + public appData: any; + public submitcmd: string; + + //constructor( public dialogRef: MatDialogRef<LaunchDialogComponent>, + constructor( + private domSanitizer: DomSanitizer, + private renderer: Renderer2, + private tesService: TesService, + ) { + this.submitcmd = null; + this.appData = null + this.subscriptions = []; } ngOnInit() { + console.log('creating launch-dialog'); this.rmListen = this.renderer.listen('window','message',(event) => this.receiveMessage(event)); this.setFocus = false; - this.sub = this.submitAppService.readyToLaunch.subscribe((ready) => this.becomesReady(ready)) - } - - ngAfterViewInit() { - console.log('in AfterViewInit, disabling the button'); - this.launchbtn.nativeElement._disabled = true; - } - - - cancel() { - console.log('cause app cancel'); - this.submitAppService.submitcmd.next(null); - this.submitAppService.appData.next(null) - this.dialogRef.close() + this.subscriptions.push(this.appSubject.subscribe(() => { this.setReady(false); this.app = this.appSubject.value; this.updateURLs()})); + } - launch() { - console.log('cause app submission'); - console.log(this.batchbuilderiframe); - this.batchbuilderiframe.nativeElement.contentWindow.postMessage('launch selected',"*"); - //this.dialogRef.close(); - //this.submitAppService.launch(); + updateURLs() { + let theme = ""; + theme=localStorage.getItem('strudel-theme'); + if (theme == null) { + theme = 'strudel-theme-light'; + } + if (this.identity !== null && this.app !== null) { + this.batchcmdurl = this.identity.site.url+"/"+this.app.name+"?theme="+theme; + } + this.batchcmdsafeurl = this.domSanitizer.bypassSecurityTrustResourceUrl(this.batchcmdurl); + if (this.app !== null) { + this.appconfigurl = this.app.url+"?theme="+theme; + } + if (!(this.appconfigurl == null)) { + this.appconfigsafeurl = this.domSanitizer.bypassSecurityTrustResourceUrl(this.appconfigurl); + } + console.log('updatedURLs'); } - ngOnDestroy() { - console.log('dialog in onDestroy'); this.rmListen(); - this.sub.unsubscribe(); + for (let s of this.subscriptions) { + s.unsubscribe(); + } } - - - - becomesReady(ready) { - + setReady(ready) { if (this.launchbtn === undefined) { return } if (ready && !(this.setFocus)) { - console.log('reenabling the launch button'); this.launchbtn.nativeElement._disabled = false; this.launchbtn.nativeElement.focus(); this.setFocus = true; @@ -93,17 +97,47 @@ export class LaunchDialogComponent implements OnInit { receiveMessage(event) { if (event.data['batchcmd'] !== undefined) { - this.submitAppService.submitcmd.next(event.data['batchcmd']); + this.submitcmd = event.data['batchcmd']; console.log('recieved a submit cmd'); - console.log('recieveMessage'+this.submitAppService.submitcmd.value); + console.log('recieveMessage'+this.submitcmd); } if (event.data['appData'] !== undefined) { - this.submitAppService.appData.next(event.data['appData']); + this.appData = event.data['appData']; } - if (event.data == "close iframe") { - console.log('recieved close iframe'); - timer(0).subscribe(() => { console.log("timer "+this.submitAppService.submitcmd.value) ; this.dialogRef.close(); this.submitAppService.launch()}); // complete the current event loop, ie receive all messages to get teh correct time before submitting - //this.submitAppService.launch(); + if (event.data['resize'] !== undefined) { + console.log('setting iframe size'); + this.height = event.data['resize']['height']; + this.width = event.data['resize']['width']; + console.log(this.height); } } + + launch() { + // tell the batchbuilder iframe that lanch was selected + // This is the queue to save the values as defaults for next launch + this.batchbuilderiframe.nativeElement.contentWindow.postMessage('launch selected',"*"); + let bi = new BatchInterface(); + bi.submitcmd = this.submitcmd; + if (this.appData != null){ + let appparams = this.appData; + this.tesService.submit(this.app,this.identity,bi,appparams); + } else { + this.tesService.submit(this.app,this.identity,bi); + } + } + + clearInfo() { + this.appData = null; + this.submitcmd = null; + } + + canILaunch() { + if (this.submitcmd != null) { + if (this.app.url == null || this.appData != null ) { + this.setReady(true); + return; + } + } + this.setReady(false); + } } diff --git a/src/app/launcher/launcher.component.html b/src/app/launcher/launcher.component.html index ff29b7a5dcd29e0457ac752a9be8a9065569dcff..e19f1d79f842ca026bbd04ca83e24013b89a0247 100644 --- a/src/app/launcher/launcher.component.html +++ b/src/app/launcher/launcher.component.html @@ -15,37 +15,74 @@ <div fxLayout="column" style="height: 100%"> <div> <mat-accordion> - <div *ngFor="let id of computeSitesService.appidentities | async"> - <mat-expansion-panel (click)=selectId(id)> + <div *ngFor="let id of (computeSitesService.appidentities | async)"> + <mat-expansion-panel (opened)="selectId(id)"> <mat-expansion-panel-header> - {{ id.displayName() }} + <mat-panel-title> + <button mat-button style="width: 100%"> + <div fxLayout="row"> + <div style="width: 10px;"></div> + <div fxFlex fxLayout="column"> + <div style="height: 10px"></div> + <span fxFlex matBadge="{{ (id.healthalerts | async).length }}" + [matBadgeHidden]= "(id.healthalerts | async).length == 0" + matBadgePosition="above before" + matBadgeColor="warn" style=" text-align: left"> + {{ id.displayName() }} + </span> + </div> + </div> + </button> + </mat-panel-title> </mat-expansion-panel-header> - <app-strudelapplist [applist]=id.site.appCatalog [identity]="id"></app-strudelapplist> + <app-strudelapplist [applist]=id.site.appCatalog [identity]="id" (appChange)="selectApp($event)"></app-strudelapplist> </mat-expansion-panel> - </div> + </div> </mat-accordion> </div> <div fxFlex></div> - <mat-menu #actionmenu="matMenu"> - - <div *ngIf="(computeSitesService.appidentities | async).length > 0"> - <button mat-menu-item (click)="logout()"><mat-icon>logout</mat-icon>Logout</button> - </div> - <button mat-menu-item routerLink="/settings"><mat-icon>settings</mat-icon>Settings</button> - <button mat-menu-item routerLink="/login">Login</button> - </mat-menu> - <button mat-icon-button [matMenuTriggerFor]="actionmenu"> - <mat-icon>more_vert</mat-icon> - </button> + <mat-menu #actionmenu="matMenu"> + <div *ngFor="let az of (authService.loggedInAuthZ | async)"> + <button mat-menu-item routerLink="/logout"><mat-icon>logout</mat-icon>Log out of {{ az.name }}</button> + </div> + <button mat-menu-item routerLink="/settings"><mat-icon>settings</mat-icon>Settings</button> + <button mat-menu-item routerLink="/login"><mat-icon>exit_to_app</mat-icon>Login</button> + </mat-menu> + <button mat-icon-button [matMenuTriggerFor]="actionmenu"> + <mat-icon>person</mat-icon> + </button> </div> </mat-sidenav> + <mat-sidenav-content> + <div fxLayout="column" style="height: 100%"> + + <!-- The dialog asking how many resources to use to run a job --> + <div *ngIf="app !== null && identity !== null" > + <app-launch-dialog [identity]="identitySubject | async" [appSubject]="appSubject"></app-launch-dialog> + </div> + + + <!-- the list of warning either on the computer system or the users account --> + <div *ngIf="(identitySubject | async) !== null" > + <mat-list> + <div *ngFor="let h of ((identitySubject | async).healthalerts | 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 list of running jobs --> + <app-joblist [identitySubject]="identitySubject" [appSubject]="appSubject"></app-joblist> -<app-joblist [identity]="(identitySubject | async)"></app-joblist> -<!--<router-outlet></router-outlet>--> - <div fxFlex></div> + </div> + </mat-sidenav-content> </mat-sidenav-container> -</div> -</div> <!-- <app-joblist></app-joblist> --> diff --git a/src/app/launcher/launcher.component.ts b/src/app/launcher/launcher.component.ts index 598eb7a344540d71183c6464cb4d97ded6c622e2..192f99c9518e73e52be553b960874bc2e8d14c63 100644 --- a/src/app/launcher/launcher.component.ts +++ b/src/app/launcher/launcher.component.ts @@ -3,9 +3,11 @@ import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatDialogModule, MatDialogCon import { Location } from '@angular/common'; // import { HttpClientModule, HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; +import { Subscription } from 'rxjs'; import { fromEvent } from 'rxjs'; import { timer } from 'rxjs/observable/timer'; import { repeat } from 'rxjs/operators'; +import { Router, NavigationStart } from '@angular/router'; import {Strudelapp} from '../strudelapp'; import { StrudelappsService } from '../strudelapps.service'; @@ -21,8 +23,6 @@ import { ComputesitesService } from '../computesites.service'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; -import {OverlayContainer} from '@angular/cdk/overlay'; - @@ -37,41 +37,42 @@ export class LauncherComponent implements OnInit { public strudelapps: Strudelapp[]; @Input() menuopen: boolean; - public app: Strudelapp; + public appSubject: BehaviorSubject<Strudelapp>; public authorised: boolean; - public identity: Identity; - // public identitySubject: BehaviorSubject<Identity>; + //public identity: Identity; + public identitySubject: BehaviorSubject<Identity>; public identities: Identity[]; public sshauthzservers: SshAuthzServer[]; private launchwindow: any; private launchwindowWatcher: any; + private subscriptions: Subscription[]; constructor( public dialog: MatDialog, public tesService: TesService, public backendSelectionService: BackendSelectionService, public authService: AuthorisationService, public computeSitesService: ComputesitesService, - public overlayContainer: OverlayContainer, + private router: Router, ) { - this.authService.sshAuthzServers.subscribe(o => {this.updateSshAuthZServers(o)}); - this.computeSitesService.appidentities.subscribe(o => this.verifyIdValid(o)); + this.subscriptions = []; + this.subscriptions.push(this.authService.sshAuthzServers.subscribe(o => {this.updateSshAuthZServers(o)})); + this.subscriptions.push(this.computeSitesService.appidentities.subscribe(o => this.navLogin(o))); + this.subscriptions.push(this.computeSitesService.appidentities.subscribe(o => this.getHealth(o))); + this.appSubject = new BehaviorSubject<Strudelapp>(null); + this.identitySubject = new BehaviorSubject<Identity>(null); + } + + navLogin(o) { + if (o.length == 0) { + this.router.navigate(['/login']); + } } - verifyIdValid(o) { - let currentId = this.tesService.identitySubject.value - if (currentId === undefined || currentId == null) { - return; - } - for (let id of this.computeSitesService.appidentities.value) { - if (currentId == id) { - return; - } - } - this.tesService.identitySubject.next(null); - //if (!(this.identitySubject.value in this.computeSitesService.appidentities.value)) { - // this.identitySubject.next(null); - //} + getHealth(o) { + for (let id of o) { + this.tesService.getHealthAlerts(id); + } } updateSshAuthZServers(o) { @@ -83,31 +84,20 @@ export class LauncherComponent implements OnInit { } - - logout() { - let dialogRef = this.dialog.open(LogoutdialogComponent, { - width: '250px', - height: '400px', - }); + ngOnDestroy() { + for (let s of this.subscriptions) { + s.unsubscribe(); + } } - login (sshauthzserver) { - this.authService.login(sshauthzserver); + selectId(id: Identity) { + this.identitySubject.next(id); + this.appSubject.next(null); } - signup(sshauthzserver) { - window.open(sshauthzserver.signup); + selectApp(app: Strudelapp) { + this.appSubject.next(app); } - selectId(id: Identity) { - this.identity=id; - this.tesService.identitySubject.next(id); - this.tesService.clearJobs(); - this.tesService.getJobs(id); - this.tesService.cancelHealthRequests(); - this.tesService.getUserHealth(id); - this.tesService.getCachetIncidents(id); - } - } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html index f61ef29bbdb343dee5196e86e35eeffde715521d..93f7c3b39d4592b6cbc9e19cb6d05a9afb34db3c 100644 --- a/src/app/login/login.component.html +++ b/src/app/login/login.component.html @@ -1,3 +1,12 @@ + +<mat-toolbar color="primary" style="width: 100%"> + <mat-toolbar-row> + <button mat-icon-button ><mat-icon>menu</mat-icon></button> + + <span>Strudel v2.0</span> + <span class="fill-horizontal-space"></span> + </mat-toolbar-row> +</mat-toolbar> <mat-sidenav-container style="height: 100%; width: 100%"> <mat-sidenav> </mat-sidenav> @@ -18,8 +27,10 @@ </mat-form-field> </div> <div style="width: 100%"> - <button fxFlex mat-button (click)="login()">Login</button> - <button fxFlex mat-button routerLink="/launch">Cancel</button> + <button fxFlex mat-button (click)="login()">Login</button> + <div fxFlex *ngIf="(authService.loggedInAuthZ | async).length > 0"> + <button fxFlex mat-button routerLink="/launch">Cancel</button> + </div> </div> <div *ngIf="selected !== undefined"> {{ selected.desc }} diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts index a0dd4a5f5aa076a67d3b3da103cd73cefb2144b2..6b2f81307e2ef5564676b520bf1efca6b0c7fa9a 100644 --- a/src/app/login/login.component.ts +++ b/src/app/login/login.component.ts @@ -2,6 +2,8 @@ import { Component, OnInit } from '@angular/core'; import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatDialogModule, MatDialogConfig } from '@angular/material'; import {OverlayContainer} from '@angular/cdk/overlay'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import { Subscription } from 'rxjs'; +import { Router, NavigationStart } from '@angular/router'; import {Strudelapp} from '../strudelapp'; import { StrudelappsService } from '../strudelapps.service'; @@ -21,12 +23,10 @@ import { ComputesitesService } from '../computesites.service'; }) export class LoginComponent implements OnInit { public strudelapps: Strudelapp[]; - public themeSubject: BehaviorSubject<string>; public app: Strudelapp; public authorised: boolean; public identity: Identity; - // public identitySubject: BehaviorSubject<Identity>; public identities: Identity[]; public sshauthzservers: SshAuthzServer[]; @@ -39,6 +39,7 @@ export class LoginComponent implements OnInit { public themes: any[] = [ {'name':'Light','value':'strudel-light-theme'}, {'name': 'Dark','value':'strudel-dark-theme'}] public selected: any; + private subscriptions: Subscription[]; constructor( public dialog: MatDialog, @@ -47,14 +48,13 @@ export class LoginComponent implements OnInit { public authService: AuthorisationService, public computeSitesService: ComputesitesService, public overlayContainer: OverlayContainer, + private router: Router, ) { - this.authService.sshAuthzServers.subscribe(o => {this.updateSshAuthZServers(o)}); - this.computeSitesService.appidentities.subscribe(o => this.verifyIdValid(o)); - this.backendSelectionService.apiserver.subscribe((s) => this.selectedApiServer = s); - this.themeSubject = new BehaviorSubject<string>('strudel-dark-theme'); - this.themeSubject.subscribe(v => this.setTheme(v)); + this.subscriptions = [] + this.subscriptions.push(this.authService.sshAuthzServers.subscribe(o => {this.updateSshAuthZServers(o)})); + } updateSshAuthZServers(o) { @@ -62,54 +62,29 @@ export class LoginComponent implements OnInit { } ngOnInit() { - this.strudelapps = []; - var lstheme: string; - try { - console.log('retrieve theme from localStorage'); - lstheme=localStorage.getItem('strudel-theme'); - console.log(lstheme) - } catch { + //this.subscriptions.push(this.computeSitesService.appidentities.subscribe(o => this.navLaunch(o))); + let navlaunch = localStorage.getItem('strudel-navlaunch') + if (navlaunch == 'true') { + localStorage.setItem('strudel-navlaunch',null) + this.subscriptions.push(this.computeSitesService.appidentities.subscribe(o => this.navLaunch(o))); } - if (lstheme == null) { - console.log('no theme set, using light'); - lstheme = 'strudel-light-theme'; - } - //Set the value of this.theme so that the selection shows a value rather than a blank - console.log('trying to find the initial value for theme'); - for (let t of this.themes) { - if (t.value == lstheme) { - console.log('found the initial value',t); - this.theme = t; - } - } - this.setTheme(lstheme); - } - verifyIdValid(o) { - let currentId = this.tesService.identitySubject.value - if (currentId === undefined || currentId == null) { - return; - } - for (let id of this.computeSitesService.appidentities.value) { - if (currentId == id) { - return; - } - } - this.tesService.identitySubject.next(null); - //if (!(this.identitySubject.value in this.computeSitesService.appidentities.value)) { - // this.identitySubject.next(null); - //} + ngOnDestroy() { + for (let s of this.subscriptions) { + s.unsubscribe(); + } } - logout() { - let dialogRef = this.dialog.open(LogoutdialogComponent, { - width: '250px', - height: '400px', - }); + navLaunch(o) { + if (this.computeSitesService.appidentities.value.length > 0) { + this.router.navigate(['/launch']); + } } + login () { + localStorage.setItem('strudel-navlaunch','true'); this.authService.login(this.selected); } @@ -117,22 +92,6 @@ export class LoginComponent implements OnInit { window.open(sshauthzserver.signup); } - selectSite(event: any) { - console.log('selecteSite'); - console.log(event); - console.log(this.selected); - } - - selectId(id: Identity) { - this.identity=id; - this.tesService.identitySubject.next(id); - this.tesService.clearJobs(); - this.tesService.getJobs(id); - this.tesService.cancelHealthRequests(); - this.tesService.getUserHealth(id); - this.tesService.getCachetIncidents(id); - } - loadConfig(event) { const reader = new FileReader(); reader.onload = (e: any) => { @@ -183,7 +142,6 @@ export class LoginComponent implements OnInit { } selectTheme(event) { - this.themeSubject.next(event.value); localStorage.setItem('strudel-theme',event.value); } } diff --git a/src/app/logout/logout.component.css b/src/app/logout/logout.component.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/logout/logout.component.html b/src/app/logout/logout.component.html new file mode 100644 index 0000000000000000000000000000000000000000..f90533663255ada7158eb5e0dffde6a7d10851e6 --- /dev/null +++ b/src/app/logout/logout.component.html @@ -0,0 +1,10 @@ +<mat-toolbar color="primary"> + <mat-toolbar-row> + <button mat-icon-button ><mat-icon>menu</mat-icon></button> + + <span>Strudel v2.0</span> + <span class="fill-horizontal-space"></span> + </mat-toolbar-row> + </mat-toolbar> + +<button mat-button (click)="logout()">Logout</button><button mat-button routerLink="/launch">Cancel</button> diff --git a/src/app/logout/logout.component.spec.ts b/src/app/logout/logout.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c04dbe281b80e2b989d1b16dab894b5b425813b9 --- /dev/null +++ b/src/app/logout/logout.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LogoutComponent } from './logout.component'; + +describe('LogoutComponent', () => { + let component: LogoutComponent; + let fixture: ComponentFixture<LogoutComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LogoutComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LogoutComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/logout/logout.component.ts b/src/app/logout/logout.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..780cc93219a2a054d7e686381d71a3760b38de90 --- /dev/null +++ b/src/app/logout/logout.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; +import { Router, NavigationStart } from '@angular/router'; + +import { AuthorisationService } from '../authorisation.service'; + +@Component({ + selector: 'app-logout', + templateUrl: './logout.component.html', + styleUrls: ['./logout.component.css'] +}) +export class LogoutComponent implements OnInit { + + constructor( + private authService: AuthorisationService, + private router: Router, + ) { + } + + ngOnInit() { + } + + logout() { + this.authService.logout(); + this.router.navigate(['/launch']); + } + +} diff --git a/src/app/strudelapplist/strudelapplist.component.html b/src/app/strudelapplist/strudelapplist.component.html index 836982dceeb8d721a07e84c221ab04ea09646ae1..f5dac24669ac08b503755f600a4cfdd8ffeadc66 100644 --- a/src/app/strudelapplist/strudelapplist.component.html +++ b/src/app/strudelapplist/strudelapplist.component.html @@ -1,16 +1,11 @@ +<mat-list style="margin-left: 30px"> <div *ngFor="let app of applist | async"> <div *ngIf="app.startscript != null"> - <button mat-button (click)=configure(app)>{{ app.name }}</button> + <button mat-button (click)=select(app) style="width: 100%; text-align: right">{{ app.name }}</button> </div> <!-- The following allows for a nested set of apps --> <div *ngIf="app.applist != null"> - <mat-accordion> - <mat-expansion-panel> - <mat-expansion-panel-header> - {{ app.name }} - </mat-expansion-panel-header> - <app-strudelapplist [applist]=app.applist [identity]=identity></app-strudelapplist> - </mat-expansion-panel> - </mat-accordion> + <app-strudelapplist [applist]=app.applist [identity]=identity (appChange)="select($event)"></app-strudelapplist> </div> </div> +</mat-list> diff --git a/src/app/strudelapplist/strudelapplist.component.ts b/src/app/strudelapplist/strudelapplist.component.ts index d2add9f2b1faac2edf9e414ccd2e7cf1dfc0e941..23c39ea48e154631d3c642da38f27a7db6161442 100644 --- a/src/app/strudelapplist/strudelapplist.component.ts +++ b/src/app/strudelapplist/strudelapplist.component.ts @@ -1,8 +1,7 @@ -import { Component, OnInit, Input } from '@angular/core'; +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import {BehaviorSubject} from 'rxjs/BehaviorSubject'; import {Strudelapp} from '../strudelapp'; -import { SubmitAppService } from '../submit-app.service'; import { Identity } from '../identity'; import { repeat } from 'rxjs/operators'; import { BatchInterface} from '../batchinterface'; @@ -10,6 +9,7 @@ import { timer } from 'rxjs/observable/timer'; import { LaunchDialogComponent } from '../launch-dialog/launch-dialog.component'; import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material'; import { DomSanitizer} from '@angular/platform-browser'; +import { TesService } from '../tes.service'; @@ -22,36 +22,21 @@ import { DomSanitizer} from '@angular/platform-browser'; export class StrudelapplistComponent implements OnInit { @Input() applist: Strudelapp[]; @Input() identity: Identity; + @Output() appChange: EventEmitter<Strudelapp>; + @Input() app: Strudelapp; - constructor(private submitAppService: SubmitAppService, public dialog: MatDialog, - private domSanitizer: DomSanitizer) { + constructor( + private tesService: TesService, + private domSanitizer: DomSanitizer) { + this.appChange = new EventEmitter<Strudelapp>(); } ngOnInit() { } - // openLaunchWindow(app: Strudelapp) { - // this.submitAppService.launchAppModal(app,this.identity); - // } - - configure(app: Strudelapp) { - // this.submitAppService.configApp.next({app: app, this.identity}); - this.submitAppService.app.next(app); - this.submitAppService.identity.next(this.identity); - if (app.url == null) { - let dialogRef = this.dialog.open(LaunchDialogComponent, { - width: '40vw', - height: '60vh' - }) - } else { - let dialogRef = this.dialog.open(LaunchDialogComponent, { - width: '95vw', - height: '95vh' - }) - } - // this.route.navigate['configlaunch']; + select(app: Strudelapp) { + this.appChange.emit(app); } - } diff --git a/src/app/submit-app.service.ts b/src/app/submit-app.service.ts index 97d34acce07e40529112882509a659b2a191629a..43b2e68eb3e22246b389772d3aae91834d8cd286 100644 --- a/src/app/submit-app.service.ts +++ b/src/app/submit-app.service.ts @@ -59,6 +59,7 @@ export class SubmitAppService { this.identity.subscribe(() => this.clearInfo()); this.submitcmd.subscribe(() => this.canILaunch()); this.appData.subscribe(()=> this.canILaunch()); + this.app.subscribe(() => { console.log('app change'); console.log(this.app.value);}); } launch() { diff --git a/src/app/tes.service.ts b/src/app/tes.service.ts index cde58cbe9aa4ed51018a410b1fd86b0f68ea5efd..9a633270e9b4c143a7c40edbd8800016413be9b2 100644 --- a/src/app/tes.service.ts +++ b/src/app/tes.service.ts @@ -37,14 +37,12 @@ private twsproxy: string; // public Base='http://localhost:5000'; public statusMsg: BehaviorSubject<any>; public jobs: any[]; -public busy: BehaviorSubject<boolean> ; // public joblist: BehaviorSubject<{ [id: string ]: Job[]}>; -public joblist: BehaviorSubject<Job[]>; -public userhealth: BehaviorSubject<Health[]>; +//public joblist: BehaviorSubject<Job[]>; +//public userhealth: BehaviorSubject<Health[]>; private timerSubscription: any; private appwindow: any; -private appwindowWatcher: any; public apiserver: BehaviorSubject<APIServer>; public apiservers: BehaviorSubject<APIServer[]>; private updateJobSub: Subscription; @@ -54,6 +52,7 @@ private nextUpdate: Subscription; private cancelRequests: Subject<string>; public theme: BehaviorSubject<string>; public identitySubject: BehaviorSubject<Identity>; +public appSubject: BehaviorSubject<Strudelapp>; // public batchinterface: {[id: string] : BatchInterface}; @@ -65,18 +64,16 @@ public identitySubject: BehaviorSubject<Identity>; private backendSelectionService: BackendSelectionService, private location: Location ) { - this.busy = new BehaviorSubject<boolean>(false); // this.joblist = new BehaviorSubject<{[id: string]: Job[]}>({}); - this.joblist = new BehaviorSubject<Job[]>([]); - this.cachetincidents = new BehaviorSubject<Health[]>([]); - this.userhealth = new BehaviorSubject<Health[]>([{'stat':'ok','msg':''}]) + //this.joblist = new BehaviorSubject<Job[]>([]); + //this.cachetincidents = new BehaviorSubject<Health[]>([]); + //this.userhealth = new BehaviorSubject<Health[]>([{'stat':'ok','msg':''}]) this.apiserver = new BehaviorSubject<APIServer>(null); this.apiservers = new BehaviorSubject<APIServer[]>([]); this.cancelRequests = new Subject<string>(); - this.identitySubject = new BehaviorSubject<Identity>(null); + //this.identitySubject = new BehaviorSubject<Identity>(null); + //this.appSubject = new BehaviorSubject<Strudelapp>(null); - this.timerSubscription = null; - this.appwindowWatcher = null; this.backendSelectionService.apiserver.subscribe( (value) => { if (value != null) {this.twsproxy = value.tws ; this.Base = value.tes }}); this.theme = new BehaviorSubject('strudel-light-theme'); // this.batchinterface = {}; @@ -115,7 +112,7 @@ public setStatusMsg(statusMsg: BehaviorSubject<any>) { var joblist: Job[] = [] var jobquery: Job[] = <Job[]>resp; - var lastjoblist: Job[] = this.joblist.value; + var lastjoblist: Job[] = identity.joblist.value; var qjobids: any[] = []; var jobids: any[] = []; var j: Job; @@ -158,9 +155,8 @@ public setStatusMsg(statusMsg: BehaviorSubject<any>) { } } joblist = joblist.sort((a,b) => (a.jobid < b.jobid)? 1:-1); - this.joblist.next(joblist); + identity.joblist.next(joblist); this.statusMsg.next(null); - this.nextUpdate = timer(5000).subscribe(() => this.getJobs(identity)); } private getBatchInterfaceError(error: any) { @@ -168,28 +164,25 @@ public setStatusMsg(statusMsg: BehaviorSubject<any>) { } - public clearJobs() { - this.joblist.next([]); - } - private getCachetIncidentsError(error: any) { - return this.getJobsError(error) + private getCachetIncidentsError(error: any, identity: Identity) { + return this.getJobsError(error, identity) } public cancelHealthRequests() { - this.cancelRequests.next('cancel') - this.cachetincidents.next([]) - this.userhealth.next([]) + this.cancelRequests.next('cancel'); } - private getUserHealthError(error: any) { + private getUserHealthError(error: any, identity: Identity) { console.log('user health error'); - return this.getJobsError(error) + identity.healthalerts.next([]); + this.statusMsg.next("There was an error checking your user account"); + //return this.getJobsError(error,identity) } - private getJobsError(error: any) { - this.joblist.next([]); + private getJobsError(error: any, identity: Identity) { + identity.joblist.next([]); if (error.status == 0) { this.statusMsg.next("A network error occurred. Please try again later"); return @@ -212,11 +205,7 @@ public setStatusMsg(statusMsg: BehaviorSubject<any>) { let headers = new HttpHeaders(); let options = { headers: headers, withCredentials: true}; // remove from the job list any jobs for identities that we don't know about - let oldjobs = this.joblist.value; - if ( identity === undefined) { - this.joblist.next([]); - return - } + let oldjobs = identity.joblist.value; let bi = new BatchInterface(); bi.cancelcmd = identity.site.cancelcmd; bi.statcmd = identity.site.statcmd; @@ -225,24 +214,9 @@ public setStatusMsg(statusMsg: BehaviorSubject<any>) { params.set('host',JSON.stringify(identity.site.host)); params.set('username',JSON.stringify(identity.username)); - // updateJobSub indicates that a request is already in process. We can cancel it - // we've probably moved to a different server - if (this.updateJobSub !== undefined) { - if (!this.updateJobSub.closed) { - this.updateJobSub.unsubscribe(); - } - } // next update indicates that we will refresh the joblist in 5 seconds (or whatever) - // In theory I think it should unsubsribe on its own since its a timer that should emit one value - // but unsubscribe just to be sure. - if (this.nextUpdate !== undefined) { - if (!(this.nextUpdate.closed)) { - this.nextUpdate.unsubscribe() - } - } - this.updateJobSub = this.http.get<Job[]>(this.Base+'/stat'+'?'+params.toString(),options) .subscribe(resp => this.updateJoblist(resp, identity), - error => this.getJobsError(error)); + error => this.getJobsError(error, identity)); } getUserHealth(identity: Identity) { @@ -254,17 +228,21 @@ getUserHealth(identity: Identity) { return } if (identity.site.userhealth === undefined) { - this.userhealth.next([{'stat':'ok','msg':''}]) return } - console.log('in get User Health'); params.set('statcmd',JSON.stringify(identity.site.userhealth)); params.set('host',JSON.stringify(identity.site.host)); params.set('username',JSON.stringify(identity.username)); this.updateUserHealthSub = this.http.get<Health[]>(this.Base+'/stat'+'?'+params.toString(),options) .pipe(takeUntil(this.cancelRequests)) - .subscribe(resp => this.userhealth.next(resp), error => this.getUserHealthError(error)); + .subscribe(resp => this.addUserHealth(identity,resp), error => this.getUserHealthError(error,identity)); +} + +getHealthAlerts(identity: Identity) { + identity.healthalerts.next([]); + this.getCachetIncidents(identity); + this.getUserHealth(identity); } getCachetIncidents(identity: Identity) { @@ -273,37 +251,43 @@ getCachetIncidents(identity: Identity) { // remove from the job list any jobs for identities that we don't know about let params = new URLSearchParams(); - - if (identity === undefined) { - return - } if (identity.site.cacheturis === undefined || identity.site.cacheturis.length == 0) { - this.cachetincidents.next([]) return } for (let uri of identity.site.cacheturis) { this.http.get(uri,options) .pipe(takeUntil(this.cancelRequests)) - .subscribe(resp => this.addCachetIncidents(resp), error => this.getCachetIncidentsError(error)); + .subscribe(resp => this.addCachetIncidents(identity,resp), error => this.getCachetIncidentsError(error,identity)); } } -addCachetIncidents(resp) { - let ci = this.cachetincidents.value - console.log(resp); +addCachetIncidents(identity,resp) { + let ci = identity.healthalerts.value; for (let i of resp.data) { - console.log(i); if (i.status == 3 || i.status == 4) { - console.log('incident resolved, continuing'); continue; } let h = new Health(); - console.log('creating new health'); h.stat = 'error'; h.msg = i.message; ci.push(h); } - this.cachetincidents.next(ci) + identity.healthalerts.next(ci); +} + +addUserHealth(identity,resp) { + let ci = identity.healthalerts.value; + console.log('in add User HEalth'); + for (let i of resp) { + if (i.stat != 'ok') { + let h = new Health(); + h.stat = 'error'; + h.msg = i.message; + ci.push(h); + } + } + identity.healthalerts.next(ci); + console.log('id health is',identity.healthalerts.value); } @@ -322,7 +306,6 @@ addCachetIncidents(resp) { } else { this.statusMsg.next('Job submission failed'); } - this.busy.next(false); } } cancelError(error: any) { @@ -333,7 +316,6 @@ addCachetIncidents(resp) { } else { this.statusMsg.next('Job submission failed'); } - this.busy.next(false); } } @@ -345,7 +327,6 @@ addCachetIncidents(resp) { let headers = new HttpHeaders(); let options = { headers: headers, withCredentials: true}; this.statusMsg.next('Submitting job'); - this.busy.next(true); let paramstr = this.buildParams(app,identity,batchinterface); // let body = this.buildBody(app,appparams) let keys = JSON.stringify(this.authorisationService.getKeys()); @@ -357,7 +338,6 @@ addCachetIncidents(resp) { let body = {'app': app, 'appparams': appparams, 'keys': keys, 'ids': JSON.stringify(JSON.stringify(ids))} this.http.post<any>(this.Base+'/submit'+'?'+paramstr, body, options) .subscribe(resp => { - this.busy.next(false); this.statusMsg.next(null) }, error => this.submissionError(error)); @@ -365,7 +345,6 @@ addCachetIncidents(resp) { submitted(resp: any, identity: Identity ) { console.log('submitted',resp); - this.busy.next(false); this.getJobs(identity); } @@ -393,13 +372,14 @@ addCachetIncidents(resp) { let username = job.identity.username; let loginhost = job.identity.site.host; let batchhost = job.batch_host; + let jobid = job.jobid; let params = new URLSearchParams; let headers = new HttpHeaders(); let options = { headers: headers, withCredentials: true}; params.set('cmd',JSON.stringify(job.app.paramscmd)); let paramstr = params.toString(); job.connectionState = 1; - this.http.get<string>(this.Base+'/appinstance/'+username+'/'+loginhost+'/'+batchhost+'?'+paramstr, options) + this.http.get<string>(this.Base+'/appinstance/'+username+'/'+loginhost+'/'+batchhost+'/'+jobid+'?'+paramstr, options) // .pipe(catchError(this.handleError)) .subscribe(resp => { job.appinst = resp; this.createTunnel(job) }, error => { this.handleAppInstanceError(job,error) }) @@ -453,32 +433,18 @@ addCachetIncidents(resp) { var re = /^https:\/\/([a-z0-9\.-]+)\/?/; let twshost = this.twsproxy.replace(re,"$1"); let windowloc = url.replace(/\{twsproxy\}/g,this.twsproxy).replace(/twshost/g,twshost); - //let windowloc = url - - // let windowloc = this.router.config - this.appwindow = window.open(windowloc); + this.appwindow = window.open(windowloc); if (this.appwindow == null) { this.statusMsg.next('It looks like a window failed to open. Please check your popup blocker settings (Strudel 2 needs to be able to open a window to your application'); return; } if (this.appwindow.closed) { } - if (!(this.appwindowWatcher === null)) { - this.appwindowWatcher.unsubscribe() - } - - let dialogRef = this.dialog.open(ModaldialogComponent, { - width: '600px', - }); - this.appwindowWatcher = timer(500).pipe(repeat()).subscribe(() => this.watchAppwindow(this.appwindow,dialogRef)); - // this.appwindow.location.assign(windowloc); } public connect(job: Job, appinst?: any) { - // this.statusMsg.next(null); let headers = new HttpHeaders(); let options = { headers: headers, withCredentials: true}; - // this.busy.next(true); this.getInterface(job); // getInterface will subsequently called getAppInstance, which will call createTunnel, which will openAppWindow } diff --git a/src/assets/config/authservers.json b/src/assets/config/authservers.json index 56c5c679c99270c9bca18adf10d7e961ced74d12..b6b8aa52d0500fae3ca2f0cebb6d52ff7f93ad78 100644 --- a/src/assets/config/authservers.json +++ b/src/assets/config/authservers.json @@ -1,7 +1,7 @@ [ { - "authorise": "https://sshauthz.cloud.cvl.org.au/pysshauthz/oauth2/oauth/authorize/aafcentral", + "authorise": "https://sshauthz.cloud.cvl.org.au/pysshauthz/oauth2/oauth/authorize/choose", "base": "https://sshauthz.cloud.cvl.org.au/pysshauthz/oauth2/", "client_id": "Q96kt2Vtw6S78dpORktM81DH", "sign": "https://sshauthz.cloud.cvl.org.au/pysshauthz/sign/monash_hpcid/api/v1/sign_key", @@ -11,17 +11,5 @@ "scope": "user:email", "cafp": "RSA SHA256:cmDxHrZQSPlBMUUcI/BWmruXho1XOzfXPDHSqVTwV2I", "signup": "https://docs.massive.org.au/M3/requesting-an-account.html" - }, - { - "authorise": "https://sshauthz.cloud.cvl.org.au/pysshauthz/oauth2/oauth/authorize/google", - "base": "https://sshauthz.cloud.cvl.org.au/pysshauthz/oauth2/", - "client_id": "Q96kt2Vtw6S78dpORktM81DH", - "sign": "https://sshauthz.cloud.cvl.org.au/pysshauthz/sign/monash_hpcid/api/v1/sign_key", - "logout": "https://sshauthz.cloud.cvl.org.au/pysshauthz/oauth2/logout", - "name": "CVL with Google ID", - "icon": null, - "scope": "user:email", - "cafp": "RSA SHA256:cmDxHrZQSPlBMUUcI/BWmruXho1XOzfXPDHSqVTwV2I", - "signup": "https://docs.massive.org.au/M3/requesting-an-account.html" } ] diff --git a/src/health-warn.scss b/src/health-warn.scss index 170b6ca40dca128d7df01a35a7afee1aa9c08f2a..c5cf7b0ed9eb0718f3c36196ddc42d1da1d05d3a 100644 --- a/src/health-warn.scss +++ b/src/health-warn.scss @@ -7,4 +7,9 @@ .health-warn { color: mat-color($warn); } + .mat-menu-panel { + .mat-menu-item { + background-color: mat-color($background); + } + } }