diff --git a/.env b/.env index 277077fed..3afd4626b 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -CRYOSTAT_AUTHORITY=http://localhost:8181 +CRYOSTAT_AUTHORITY=http://0.0.0.0:8181 diff --git a/src/app/Recordings/RecordingActions.tsx b/src/app/Recordings/RecordingActions.tsx index ba35bb612..9111cf189 100644 --- a/src/app/Recordings/RecordingActions.tsx +++ b/src/app/Recordings/RecordingActions.tsx @@ -82,7 +82,7 @@ export const RecordingActions: React.FunctionComponent = context.api.downloadRecording(props.recording); }, [context.api, props.recording]); - const handleDownloadReport = React.useCallback(() => { + const handleViewReport = React.useCallback(() => { context.api.downloadReport(props.recording); }, [context.api, props.recording]); @@ -93,8 +93,8 @@ export const RecordingActions: React.FunctionComponent = onClick: handleDownloadRecording }, { - title: "Download Report", - onClick: handleDownloadReport + title: "View Report ...", + onClick: handleViewReport } ]; if (grafanaEnabled) { @@ -106,7 +106,7 @@ export const RecordingActions: React.FunctionComponent = ); } return actionItems; - }, [handleDownloadRecording, handleDownloadReport, grafanaEnabled, grafanaUpload]); + }, [handleDownloadRecording, handleViewReport, grafanaEnabled, grafanaUpload]); return ( { - const req = () => - fromFetch(recording.reportUrl, { - credentials: 'include', - mode: 'cors', - headers, - }) - .pipe( - map(resp => { - if (resp.ok) return resp; - throw new HttpError(resp); - }), - catchError(err => this.handleError(err, req)), - concatMap(resp => resp.blob()), - ); - req().subscribe(resp => + const body = new window.FormData(); + body.append('resource', recording.reportUrl.replace('/api/v1', '/api/beta')); + this.sendRequest('beta', 'auth/token', { + method: 'POST', + body, + }) + .pipe( + concatMap(resp => resp.json()), + map((response: AssetJwtResponse) => response.data.result.resourceUrl) + ).subscribe(resourceUrl => { this.downloadFile( + resourceUrl, `${recording.name}.report.html`, - resp, - 'text/html') - ) - }); + false + ); + }); } downloadRecording(recording: SavedRecording): void { - this.login.getHeaders().subscribe(headers => { - const req = () => fromFetch(recording.downloadUrl, { - credentials: 'include', - mode: 'cors', - headers, - }) - .pipe( - map(resp => { - if (resp.ok) return resp; - throw new HttpError(resp); - }), - catchError(err => this.handleError(err, req)), - concatMap(resp => resp.blob()), - ); - req().subscribe(resp => + const body = new window.FormData(); + body.append('resource', recording.downloadUrl.replace('/api/v1', '/api/beta')); + this.sendRequest('beta', 'auth/token', { + method: 'POST', + body, + }) + .pipe( + concatMap(resp => resp.json()), + map((response: AssetJwtResponse) => response.data.result.resourceUrl) + ).subscribe(resourceUrl => { this.downloadFile( - recording.name + (recording.name.endsWith('.jfr') ? '' : '.jfr'), - resp, - 'application/octet-stream') - ) - }); + resourceUrl, + recording.name + (recording.name.endsWith('.jfr') ? '' : '.jfr') + ); + }); } downloadTemplate(template: EventTemplate): void { - this.target.target().pipe(concatMap(target => { - const url = `targets/${encodeURIComponent(target.connectUrl)}/templates/${encodeURIComponent(template.name)}/type/${encodeURIComponent(template.type)}`; - return this.sendRequest('v1', url) - .pipe(concatMap(resp => resp.text())); - })) - .subscribe(resp => { - this.downloadFile( - `${template.name}.jfc`, - resp, - 'application/jfc+xml') + this.target.target() + .pipe(first(), map(target => + `${this.login.authority}/api/beta/targets/${encodeURIComponent(target.connectUrl)}/templates/${encodeURIComponent(template.name)}/type/${encodeURIComponent(template.type)}` + )) + .subscribe(resource => { + const body = new window.FormData(); + body.append('resource', resource); + this.sendRequest('beta', 'auth/token', { + method: 'POST', + body, + }) + .pipe( + concatMap(resp => resp.json()), + map((response: AssetJwtResponse) => response.data.result.resourceUrl), + ).subscribe(resourceUrl => { + this.downloadFile( + resourceUrl, + `${template.name}.jfc` + ); + }); }); } @@ -455,14 +453,16 @@ export class ApiService { return req(); } - private downloadFile(filename: string, data: BlobPart, type: string): void { - const blob = new window.Blob([ data ], { type } ); - const url = window.URL.createObjectURL(blob); + private downloadFile(url: string, filename: string, download = true): void { const anchor = document.createElement('a'); - anchor.download = filename; + anchor.setAttribute('style', 'display: none; visibility: hidden;'); + anchor.target = '_blank;' + if (download) { + anchor.download = filename; + } anchor.href = url; anchor.click(); - window.setTimeout(() => window.URL.revokeObjectURL(url)); + anchor.remove(); } private handleError(error: Error, retry: () => Observable): ObservableInput { @@ -490,6 +490,22 @@ export class ApiService { } +export interface ApiV2Response { + meta: { + status: string; + type: string; + }; + data: Object; +} + +interface AssetJwtResponse extends ApiV2Response { + data: { + result: { + resourceUrl: string; + } + } +} + export interface SavedRecording { name: string; downloadUrl: string; diff --git a/src/app/Shared/Services/Login.service.tsx b/src/app/Shared/Services/Login.service.tsx index 032d96417..8d0a33600 100644 --- a/src/app/Shared/Services/Login.service.tsx +++ b/src/app/Shared/Services/Login.service.tsx @@ -39,6 +39,7 @@ import { Base64 } from 'js-base64'; import { combineLatest, Observable, ObservableInput, of, ReplaySubject } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; import { catchError, concatMap, first, map, tap } from 'rxjs/operators'; +import { ApiV2Response } from './Api.service'; import { TargetService } from './Target.service'; export enum SessionState { @@ -124,6 +125,8 @@ export class LoginService { const headers = new window.Headers(); if (!!token && !!method) { headers.set('Authorization', `${method} ${token}`) + } else if (method === AuthMethod.NONE) { + headers.set('Authorization', AuthMethod.NONE); } return headers; } @@ -131,7 +134,7 @@ export class LoginService { getHeaders(): Observable { const authorization = combineLatest([this.getToken(), this.getAuthMethod()]) .pipe( - map((parts: [string, string]) => this.getAuthHeaders(parts[0], parts[1])), + map((parts: [string, AuthMethod]) => this.getAuthHeaders(parts[0], parts[1])), first(), ); return combineLatest([authorization, this.target.target()]) @@ -155,7 +158,7 @@ export class LoginService { return this.token.asObservable(); } - getAuthMethod(): Observable { + getAuthMethod(): Observable { return this.authMethod.asObservable(); } @@ -235,17 +238,7 @@ export class LoginService { } -interface ApiResponse { - meta: Meta; - data: Object; -} - -interface Meta { - status: string; - type: string; -} - -interface AuthV2Response extends ApiResponse { +interface AuthV2Response extends ApiV2Response { data: { result: { username: string;