Commit ac23fd20 authored by Lance Wilson's avatar Lance Wilson
Browse files

Merge branch 'test' into 'prod'

Test

See merge request !89
parents c42e3221 07a54cb6
Pipeline #15686 passed with stages
in 7 minutes and 15 seconds
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}
\ No newline at end of file
......@@ -60,13 +60,19 @@ export class AppComponent {
}
private displayMessage(msg) {
private displayMessage(notification: [string,any]) {
var msg: string = notification[0]
var fn: any = notification[1]
if (msg === null || msg == "") {
if (this.snackBarRef != undefined) {
this.snackBarRef.dismiss()
}
} else {
this.snackBarRef = this.snackBar.open(msg,'Dismiss');
if (fn !== null && fn !== undefined) {
console.log('adding a subscription',fn);
this.snackBarRef.afterDismissed().subscribe(() => { console.log(fn); fn() });
}
}
}
setTheme(theme: string) {
......
......@@ -209,12 +209,13 @@ public getKeys(id?: Identity) {
tap((resp) => { console.log('agent contents',resp) }),
switchMap((resp) => of(this.addExpiryField(resp))),
tap((resp) => {
if (this.agentContents.value !== null && this.agentContents.value.length > resp.length) {
this.notifications.notify("Your login expired. Please login again");
} else {
this.notifications.notify("");
};
this.agentContents.next(resp)
// if (this.agentContents.value !== null && this.agentContents.value.length > resp.length) {
// this.notifications.notify("Your login expired. Please login again");
// } else {
// this.notifications.notify("");
// };
this.notifications.notify("");
this.agentContents.next(resp)
}),
catchError((e) => { console.error('updateAGentContents error',e) ; return of([])})
//tap((_) => this.notifications.notify(""))
......
......@@ -64,7 +64,7 @@ export class BrowserWindowService {
let twshost = this.twsproxy.replace(re,"$1");
let windowloc = url.replace(/\{twsproxy\}/g,this.twsproxy).replace(/twshost/g,twshost);
var authwindow = null;
console.log('openAppWindow entered');
if (basicAuth) {
let authwindowloc = windowloc.replace(/^https:\/\//,'https://'+appinst.username+':'+appinst.password+'@');
......@@ -111,14 +111,10 @@ export class BrowserWindowService {
public logUsage() {
var app: any;
console.log('entered log usage');
console.log(this.settingsService.logging);
if (this.settingsService.logging) {
this.openapps.forEach( (app,index) => {
console.log('probing window',app);
if (!app.window.closed) {
if (app.job.state == 'RUNNING') {
console.log('log url',environment.logserver+"/"+app.job.identity.site.name+"/"+app.job.identity.username+"/"+app.job.app.name+"/"+app.job.jobid);
this.http.get<any>(environment.logserver+"/"+app.job.identity.site.name+"/"+app.job.identity.username+"/"+app.job.app.name+"/"+app.job.jobid).pipe( //We're expecting 404 not founds
catchError((e) => {console.error(e); return of([]);})
).subscribe((v) => {console.log('log success',v); return})
......
......@@ -12,7 +12,7 @@ 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: BehaviorSubject<Strudelapp[]>;
appCatalogUri: string;
appCatalogCmd: string;
internalfirewall: boolean; // Does a firewall exist within the site necessitating an extra level of ssh tunnel
......@@ -23,18 +23,6 @@ export class Computesite {
contact: string;
}
export class Strudelapp {
url: string; // A url used to retrieve extra config options. May be null
name: string; // Human readable name
startscript: string; // batch script ... should NOT include resource directives
// resource directives like #SBATCH belong in the batchinterface
paramscmd: string; // command to return extra data such as passwords and tokens
// values returned here can be used in the client strings
client: {cmd: string[], redir: string};
localbind: boolean; // does the application bind to a port on the localhost
// interface or on all interfaces. This behaviour determins
/// how we create tunnels
}
export class Health {
stat: string;
......
import { Injectable } from '@angular/core';
import { Computesite, Strudelapp } from './computesite';
import { Computesite } from './computesite';
import { Strudelapp } from './strudelapp';
import {BehaviorSubject, of, combineLatest} from 'rxjs';
import { HttpClientModule, HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
......@@ -46,7 +47,7 @@ export class ComputesitesService {
let options = { headers: headers, withCredentials: false};
if (s.appCatalogUri !== null) {
this.http.get<Strudelapp[]>(s.appCatalogUri,options)
.pipe(catchError(this.handleError('getStrudelApps')))
.pipe(catchError(this.handleError(' s')))
.subscribe(resp => this.updateStrudelApps(s,resp));
}
}
......@@ -64,7 +65,7 @@ export class ComputesitesService {
if (ids != undefined && ids != null) {
for (let id of ids) {
this.getStrudelAppsIdentity(id)
.subscribe(resp => this.updateStrudelApps(id.site,resp));
.subscribe(resp => this.updateStrudelApps(id,resp));
}
}
}
......@@ -81,9 +82,12 @@ export class ComputesitesService {
}
private getStrudelAppsCmd(identity: Identity): Observable<Strudelapp[]> {
console.log('running getStrudleAppsCmd',identity)
if (identity.site.appCatalogCmd != null) {
return (this.tesService.runCommand(identity,identity.site.appCatalogCmd) as Observable<Strudelapp[]>)
.pipe(catchError(this.handleError('getStrudelApps', <Strudelapp[]>[])))
.pipe(
tap((v) => console.log('getapps',identity,' returned',v)),
catchError(this.handleError('getStrudelApps', <Strudelapp[]>[])))
}
return of([] as Strudelapp[])
}
......@@ -107,18 +111,18 @@ export class ComputesitesService {
}
updateStrudelApps(site: Computesite, apps) {
updateStrudelApps(id: Identity, apps) {
var sapps: Strudelapp[];
var localapps: Strudelapp[];
sapps = <Strudelapp[]>apps;
localapps = JSON.parse(localStorage.getItem(site.name+'-apps'))
localapps = JSON.parse(localStorage.getItem(id.site.name+'-apps'))
if (localapps !== null) {
for (let a of localapps) {
sapps.push(a);
}
}
site.appCatalog.next(sapps);
id.appCatalog.next(sapps);
}
private handleError<T> (operation = 'operation', result?: T) {
......@@ -160,13 +164,11 @@ export class ComputesitesService {
}
if (localcomputesites !== null ) {
for (let cs of localcomputesites) {
cs.appCatalog = new BehaviorSubject<Strudelapp[]>([]);
computesites.push(cs);
}
}
for (let cs of resp) {
let computesite = <Computesite>cs;
computesite.appCatalog = new BehaviorSubject<Strudelapp[]>([])
computesites.push(computesite);
}
this.computesites.next(computesites);
......
import {Computesite, Health} from './computesite';
import {Job} from './job';
import {BehaviorSubject} from 'rxjs';
import {Strudelapp} from './strudelapp';
// 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 {
......@@ -13,6 +14,7 @@ export class Identity {
joblist: BehaviorSubject<Job[]>;
quotas: any[];
expiry: number;
appCatalog: BehaviorSubject<Strudelapp[]>;
constructor( username: string, site: Computesite, expiry: number) {
this.username = username;
this.site = site;
......@@ -20,8 +22,10 @@ export class Identity {
this.systemalerts = new BehaviorSubject<Health[]>(null);
this.accountalerts = new BehaviorSubject<Health[]>(null);
this.joblist = new BehaviorSubject<Job[]>([]);
this.appCatalog = new BehaviorSubject<Strudelapp[]>([]);
this.quotas = [];
this.expiry = expiry;
}
copy_skip_catalog(): Identity {
......
......@@ -53,7 +53,6 @@ export class JoblistComponent implements OnInit {
{this.sub = timer(5000).pipe(repeat()).subscribe( () => this.getJobs() )});
}*/
updateJoblist(jobquery: Job[], identity: Identity) {
console.log('in updateJoblist')
var joblist: Job[] = []
var qjobids: any[] = [];
var cjobids: any[] = [];
......@@ -92,7 +91,7 @@ export class JoblistComponent implements OnInit {
// any jobs in the joblist that we don't know which application they arem try to figure it out
for (j of joblist) {
if (j.app === undefined || j.app == null) {
j.app = Strudelapp.getApp(j.appname,identity.site.appCatalog.value);
j.app = Strudelapp.getApp(j.appname,identity.appCatalog.value);
}
if (j.identity == undefined) {
j.identity = identity;
......
......@@ -33,6 +33,7 @@ export class JobsService {
public refreshJobs() {
if (this.id !== undefined && this.id !== null) {
var query$: Observable<Job[]>;
console.log('refreshJobs');
query$ = this.tes.runCommand(this.id, this.id.site.statcmd)
query$.subscribe((qjobs) => this.jobs$.next(<Job[]>qjobs),
(error) => this.getJobsError(error,this.id))
......@@ -41,17 +42,28 @@ export class JobsService {
}
public getJobsError(error,identity: Identity) {
if (error.hasOwnProperty('status') && error.status == 401) {
this.notifications.notify("Your login appears to have expired. Please log in again", () => { this.authService.updateAgentContents().subscribe((_) => {return}) } );
return;
}
this.tsub.unsubscribe();
if (identity.expiry < Date.now()) {
this.notifications.notify("Your login has expired. Please log in again", () => { this.authService.updateAgentContents().subscribe((_) => {return}) } );
return;
}
console.error('getJobsError id', identity);
if (error.hasOwnProperty("error") && error.error.hasOwnProperty("message")) {
if (error.error.message.indexOf("Permission denied") != -1) {
this.notifications.notify("Your login appears to have expired. Please log in again");
this.authService.updateAgentContents().subscribe((_) => {return});
this.notifications.notify("Your login appears to have expired. Please log in again", () => { this.authService.updateAgentContents().subscribe((_) => {return}) } );
return;
}
this.notifications.notify("Unable to retrieve a list of running jobs.\nThe error messge was " + error.error.message);
console.log(error);
return;
}
this.notifications.notify("Unable to retrieve a list of running jobs.\nDid your session expire?")
console.error(error);
this.authService.updateAgentContents().subscribe((_) => {return});
this.notifications.notify("Unable to retrieve a list of running jobs.\nThe error wasn't specified\nPlease report what you were doign via the contact us link", () => { this.authService.updateAgentContents().subscribe((_) => {return}) });
//this.authService.updateAgentContents().subscribe((_) => {return});
}
}
......@@ -118,6 +118,7 @@ export class KeygenComponent implements OnInit, OnDestroy {
var now = new Date()
var end = new Date(now.getTime() + 28*24*60*60*1000); //request a certificate valid for 28 days
//its expected that the user will terminate the session by closing their browser/sleeping their laptop before this
//var end = new Date(now.getTime() + 30*1000); // Uncomment if you want to test certificates expiring
let data = {'public_key': key.public, 'end': end.toISOString()};
return this.http.post<any>(token.sshauthzservice.sign,data, options).pipe(
tap((v) => console.log('in getCert',v)),
......
......@@ -89,7 +89,11 @@ export class LaunchDialogComponent implements OnInit {
theme = 'strudel-light-theme';
}
if (this.identity !== null && this.app !== null) {
this.batchcmdurl = this.identity.site.url+"/"+encodeURIComponent(this.app.name)+"?theme="+theme;
if (this.app.batchcmdurl !== null && this.app.batchcmdurl !== undefined) {
this.batchcmdurl = this.app.batchcmdurl+"?theme="+theme;
} else {
this.batchcmdurl = this.identity.site.url+"/"+encodeURIComponent(this.app.name)+"?theme="+theme;
}
}
this.batchcmdsafeurl = this.domSanitizer.bypassSecurityTrustResourceUrl(this.batchcmdurl);
if (this.app.url !== null) {
......
......@@ -37,7 +37,7 @@
</button>
</mat-list-item>
<app-strudelapplist [applist]=id.site.appCatalog [identity]="id" (appChange)="selectApp($event)" style="width: 100%"></app-strudelapplist>
<app-strudelapplist [applist]=id.appCatalog [identity]="id" (appChange)="selectApp($event)" style="width: 100%"></app-strudelapplist>
</mat-list>
</mat-expansion-panel>
<!--<div style="width: 100%; border-bottom: 1px solid var(--panel-border-color);"></div>-->
......
......@@ -171,6 +171,7 @@ export class LauncherComponent implements OnInit {
this.subscriptions.push(this.authService.sshAuthzServers.subscribe(o => {this.updateSshAuthZServers(o)}));
this.subscriptions.push(this.computeSitesService.identities.subscribe(o => this.navLogin(o)));
this.subscriptions.push(this.computeSitesService.appidentities.subscribe(o => this.getHealth(o)));
this.subscriptions.push(timer(60000).pipe(repeat()).subscribe(() => { this.getHealth(this.computeSitesService.appidentities.value) } ));
this.subscriptions.push(this.identity$.subscribe(o => this.jobsService.setId(o)));
}
......@@ -233,8 +234,8 @@ export class LauncherComponent implements OnInit {
if (apps === null) {
apps = 'accountinfo';
}
if (id.site.appCatalog.value === undefined || id.site.appCatalog.value === null || (id.site.appCatalog.value).length == 0) {
id.site.appCatalog.pipe(filter((v) => v !== null && v.length > 0),take(1)).subscribe(() => this.updateIdApp(params,appidentities));
if (id.appCatalog.value === undefined || id.appCatalog.value === null || (id.appCatalog.value).length == 0) {
id.appCatalog.pipe(filter((v) => v !== null && v.length > 0),take(1)).subscribe(() => this.updateIdApp(params,appidentities));
return
}
var app: Strudelapp = this.getApp(id,apps);
......@@ -247,7 +248,7 @@ export class LauncherComponent implements OnInit {
if (id == 'accountinfo') {
return null;
}
return Strudelapp.getApp(v,id.site.appCatalog.value);
return Strudelapp.getApp(v,id.appCatalog.value);
}
getId(v: any, appidentities: Identity[]) {
......
......@@ -6,30 +6,36 @@ import { BehaviorSubject } from 'rxjs';
})
export class NotificationsService {
private msg: BehaviorSubject<string>;
private msg: BehaviorSubject<[string,any]>;
private afterNotifyFn: BehaviorSubject<any>;
constructor() {
this.msg = new BehaviorSubject<string>(null);
this.msg = new BehaviorSubject<[string,any]>([null,null]);
}
getMsg() {
return this.msg;
}
notify(msg: any) {
notify(msg: any, afterFn : any = null) {
if (msg === null || msg === undefined) {
this.msg.next("");
this.msg.next(["",null]);
return
}
if (msg.hasOwnProperty('error')) {
this.notify(msg.error);
this.notify(msg.error, afterFn);
return;
}
if (msg.hasOwnProperty('message')) {
this.notify(msg.message);
this.notify(msg.message, afterFn);
return;
}
if (msg.hasOwnProperty('msg')) {
this.notify(msg.msg, afterFn);
return;
}
this.msg.next(msg);
this.msg.next([msg, afterFn]);
console.log(msg);
}
......
......@@ -8,6 +8,7 @@ export class AppAction {
}
export class Strudelapp {
url: string; // A url used to retrieve extra config options. May be null
batchcmdurl: string = null; // if an app provides a batchcommandurl it overrides the url provieded for the site.
name: string;
startscript: string; // batch script ... should NOT include resource directives
// resource directives like #SBATCH belong in the batchinterface
......
......@@ -54,7 +54,6 @@ public openWindow$: Subject<any>;
private buildParams(app: Strudelapp, identity: Identity, batchinterface: BatchInterface, appinst?: any): string {
let params = new URLSearchParams();
let id = identity.copy_skip_catalog();
id.site.appCatalog = null;
if (appinst!== null) {
params.set('appinstance',JSON.stringify(appinst));
}
......@@ -166,13 +165,17 @@ private addUserHealth(identity,resp) {
}
submissionError(error: any) {
submissionError(identity: Identity, error: any) {
if (identity.expiry < Date.now()) {
this.notifications.notify("Your login has expired. Please log in again", () => { this.authorisationService.updateAgentContents().subscribe( () => {return} ) } );
return;
}
if (error.status != 0) {
try {
this.notifications.notify(error);
console.error(error);
} catch {
this.notifications.notify('Job Canceling failed');
this.notifications.notify('Job Submission failed');
console.error(error);
}
}
......@@ -210,7 +213,7 @@ private addUserHealth(identity,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.notifications.notify(null) },
error => this.submissionError(error));
error => this.submissionError(identity, error));
}
......@@ -310,7 +313,7 @@ private addUserHealth(identity,resp) {
'action':action,'appinst':appinst});
}
job.connectionState=null},
(err) => { job.connectionState = 0; this.handleError(err)})
(err) => { job.connectionState = 0; this.handleError(job.identity, err)})
}
......@@ -354,9 +357,13 @@ public getSftpData(id: Identity, path: string, cd: string ) {
}
private handleError(error: HttpErrorResponse) {
private handleError(identity: Identity, error: HttpErrorResponse) {
console.error(error);
console.log('in handle error');
if (identity.expiry < Date.now()) {
this.notifications.notify("Your login has expired. Please log in again", () => { this.authorisationService.updateAgentContents().subscribe( () => {return} ) } );
return;
}
if (error.error instanceof ErrorEvent) {
this.notifications.notify("A networking error occured.")
return
......@@ -365,8 +372,11 @@ private handleError(error: HttpErrorResponse) {
this.notifications.notify(error);
return
}
this.notifications.notify("Your login appears to have expired. Please login again");
this.authorisationService.updateAgentContents().subscribe((v) => {return});
if (error.hasOwnProperty("status") && error.status == 400) {
this.notifications.notify(error);
return
}
this.notifications.notify(error);
}
......
......@@ -10,19 +10,5 @@
"cancelcmd": "/usr/local/strudel2_cluster/latest/bin/s2cancel {jobid}",
"statcmd": "/usr/local/strudel2_cluster/latest/bin/s2stat",
"userhealth": "/usr/local/bin/uijson"
},
{
"url": "https://beta-api.cloud.cvl.org.au/cvluwa/",
"name": "CVL@UWA",
"host": "146.118.65.246",
"dtn": "146.118.65.242",
"cafingerprint": "ECDSA SHA256:6wVXdokvvlTNcXPMc9KyvIXA8a8XNfLuhBfNOYeeMdg",
"appCatalog": [],
"appCatalogCmd": "/home/strudel2/bin/getapps",
"cancelcmd": "/home/strudel2/bin/s2cancel {jobid}",
"statcmd": "/home/strudel2/bin/s2stat",
"userhealth": "/home/strudel2/bin/uijson",
"cacheturis": [],
"contact": "<CVL@UWA Support> help@massive.org.au"
}
]
[
{
"url": "https://strudel2-api-test.cloud.cvl.org.au/m3/",
"url": "https://beta-api.cloud.cvl.org.au/m3/",
"name": "M3",
"host": "m3-login2.massive.org.au",
"host": "m3.massive.org.au",
"dtn": "m3-dtn1.massive.org.au",
"cafingerprint": "ECDSA SHA256:6wVXdokvvlTNcXPMc9KyvIXA8a8XNfLuhBfNOYeeMdg",
"appCatalog": [],
"appCatalogUri": "./assets/config/m3apps.test.json",
"cancelcmd": "/usr/local/sv2/sv2scancel.sh {jobid}",
"statcmd": "/usr/local/sv2/sv2stat.py",
"appCatalogCmd": "/usr/local/strudel2_cluster/latest/bin/getapps",
"cancelcmd": "/usr/local/strudel2_cluster/latest/bin/s2cancel {jobid}",
"statcmd": "/usr/local/strudel2_cluster/latest/bin/s2stat",
"userhealth": "/usr/local/bin/uijson"
},
{
"url": "https://beta-api.cloud.cvl.org.au/cvluwa/",
"name": "CVL@UWA",
"host": "146.118.65.246",
"dtn": "146.118.65.242",
"cafingerprint": "ECDSA SHA256:6wVXdokvvlTNcXPMc9KyvIXA8a8XNfLuhBfNOYeeMdg",
"appCatalog": [],
"appCatalogCmd": "/home/strudel2/bin/getapps",
"cancelcmd": "/home/strudel2/bin/s2cancel {jobid}",
"statcmd": "/home/strudel2/bin/s2stat",
"userhealth": "/home/strudel2/bin/uijson",
"cacheturis": [],
"contact": "<CVL@UWA Support> help@massive.org.au"
}
]
......@@ -43,7 +43,7 @@
"instactions": [
{
"name": "Connect",
"paramscmd": "/usr/local/sv2/jupyter/jupyter_params.sh {jobid}",
"paramscmd": "/usr/local/sv2/jupyter/jupyter_params.py {jobid}",
"client": {"cmd": null, "redir": "?token={token}"},
"states": ["RUNNING"]
},
......
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