From c48c05f8ee10458b85b45884b99ff8bcec2b2098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr> Date: Thu, 28 Jul 2022 16:56:02 +0200 Subject: [PATCH 1/6] feat: load local session file by its URL refs #476 --- src/app/app.module.ts | 3 ++ .../load-session-url.component.ts | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 src/app/components/load-session-url/load-session-url.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a76bf4558..04b113670 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -118,6 +118,7 @@ import { JalhydModelValidationStepDirective } from "./directives/jalhyd-model-validation.directive"; import { ImmediateErrorStateMatcher } from "./formulaire/immediate-error-state-matcher"; +import { LoadSessionURLComponent } from "./components/load-session-url/load-session-url.component"; const appRoutes: Routes = [ { path: "list/search", component: CalculatorListComponent }, @@ -126,6 +127,7 @@ const appRoutes: Routes = [ { path: "setup", component: ApplicationSetupComponent }, { path: "diagram", component: ModulesDiagramComponent }, { path: "properties", component: SessionPropertiesComponent }, + { path: "loadsession/:path", component: LoadSessionURLComponent }, { path: "**", redirectTo: "list", pathMatch: "full" } ]; @@ -217,6 +219,7 @@ const appRoutes: Routes = [ JalhydModelValidationStepDirective, JetResultsComponent, JetTrajectoryChartComponent, + LoadSessionURLComponent, LogComponent, LogDrawerComponent, LogEntryComponent, diff --git a/src/app/components/load-session-url/load-session-url.component.ts b/src/app/components/load-session-url/load-session-url.component.ts new file mode 100644 index 000000000..99c3bae84 --- /dev/null +++ b/src/app/components/load-session-url/load-session-url.component.ts @@ -0,0 +1,52 @@ +import { Component, forwardRef, Inject } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { AppComponent } from "app/app.component"; +import { HttpService } from "app/services/http.service"; + +// load a session file by its URL (either local or remote) + +@Component({ + selector: "load-session-url", + template: "" +}) +export class LoadSessionURLComponent { + private path: string; + + constructor( + @Inject(forwardRef(() => AppComponent)) private appComponent: AppComponent, + private route: ActivatedRoute, + private httpService: HttpService + ) { + } + + ngOnInit() { + // get "path" argument from URL + const path = this.route.snapshot.params.path; + + if (path.startsWith("http")) { + // general URL path + } + else { + // local path + + // input URL example : http://localhost:4200/#/loadsession/app%2Fexamples%2Fpab-complete-chain.json + // extracted path : app/examples/pab-complete-chain.json + + this.loadLocalSession(path); + } + } + + /** + * load a locally stored session file + * @param path local path in the form eg. app/examples/pab-complete-chain.json + */ + private async loadLocalSession(path: string) { + try { + const d = await this.httpService.httpGetBlobRequestPromise(path); + const f: any = new Blob([d], { type: "application/json" }); + this.appComponent.loadSessionFile(f); + } catch (e) { + alert("ERROR: session " + path + " does not exist"); + } + } +} -- GitLab From bdf77102fc551ccc12832f95c552f47a481b3b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr> Date: Fri, 29 Jul 2022 14:26:56 +0200 Subject: [PATCH 2/6] feat: load session by URL: use MatDialog to display errors refs #476 --- src/app/app.module.ts | 5 ++- .../dialog-show-message.component.html | 10 ++++++ .../dialog-show-message.component.scss | 0 .../dialog-show-message.component.ts | 34 +++++++++++++++++++ .../load-session-url.component.ts | 31 ++++++++++++++--- 5 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 src/app/components/dialog-show-message/dialog-show-message.component.html create mode 100644 src/app/components/dialog-show-message/dialog-show-message.component.scss create mode 100644 src/app/components/dialog-show-message/dialog-show-message.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 04b113670..84271156c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -119,6 +119,7 @@ import { } from "./directives/jalhyd-model-validation.directive"; import { ImmediateErrorStateMatcher } from "./formulaire/immediate-error-state-matcher"; import { LoadSessionURLComponent } from "./components/load-session-url/load-session-url.component"; +import { DialogShowMessageComponent } from "./components/dialog-show-message/dialog-show-message.component"; const appRoutes: Routes = [ { path: "list/search", component: CalculatorListComponent }, @@ -203,6 +204,7 @@ const appRoutes: Routes = [ DialogSaveSessionComponent, DialogNewPbCloisonComponent, DialogLoadPredefinedEspeceComponent, + DialogShowMessageComponent, FieldSetComponent, FieldsetContainerComponent, FixedResultsComponent, @@ -262,7 +264,8 @@ const appRoutes: Routes = [ DialogSaveSessionComponent, DialogNewPbCloisonComponent, DialogLoadSessionComponent, - DialogLogEntriesDetailsComponent + DialogLogEntriesDetailsComponent, + DialogShowMessageComponent ], providers: [ // services ApplicationSetupService, diff --git a/src/app/components/dialog-show-message/dialog-show-message.component.html b/src/app/components/dialog-show-message/dialog-show-message.component.html new file mode 100644 index 000000000..9ed2144e3 --- /dev/null +++ b/src/app/components/dialog-show-message/dialog-show-message.component.html @@ -0,0 +1,10 @@ +<h1 mat-dialog-title [innerHTML]="uitextTitle"></h1> +<div mat-dialog-content> + {{ uitextMessage }} +</div> + +<div mat-dialog-actions [attr.align]="'end'"> + <button mat-raised-button color="warn" [mat-dialog-close]="true"> + {{ uitextClose }} + </button> +</div> \ No newline at end of file diff --git a/src/app/components/dialog-show-message/dialog-show-message.component.scss b/src/app/components/dialog-show-message/dialog-show-message.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/components/dialog-show-message/dialog-show-message.component.ts b/src/app/components/dialog-show-message/dialog-show-message.component.ts new file mode 100644 index 000000000..67a3740fb --- /dev/null +++ b/src/app/components/dialog-show-message/dialog-show-message.component.ts @@ -0,0 +1,34 @@ +import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { Inject, Component } from "@angular/core"; + +@Component({ + selector: "dialog-show-message", + templateUrl: "dialog-show-message.component.html", + styleUrls: ["dialog-show-message.component.scss"] +}) +export class DialogShowMessageComponent { + + private title: string; + + private message: string; + + constructor( + public dialogRef: MatDialogRef<DialogShowMessageComponent>, + @Inject(MAT_DIALOG_DATA) data: any + ) { + this.title = data.title; + this.message = data.message; + } + + public get uitextTitle() { + return this.title; + } + + public get uitextMessage() { + return this.message; + } + + public get uitextClose() { + return "Close"; + } +} diff --git a/src/app/components/load-session-url/load-session-url.component.ts b/src/app/components/load-session-url/load-session-url.component.ts index 99c3bae84..52e98eab9 100644 --- a/src/app/components/load-session-url/load-session-url.component.ts +++ b/src/app/components/load-session-url/load-session-url.component.ts @@ -1,7 +1,10 @@ +import { Location } from "@angular/common"; import { Component, forwardRef, Inject } from "@angular/core"; +import { MatDialog } from "@angular/material/dialog"; import { ActivatedRoute } from "@angular/router"; import { AppComponent } from "app/app.component"; import { HttpService } from "app/services/http.service"; +import { DialogShowMessageComponent } from "../dialog-show-message/dialog-show-message.component"; // load a session file by its URL (either local or remote) @@ -10,12 +13,13 @@ import { HttpService } from "app/services/http.service"; template: "" }) export class LoadSessionURLComponent { - private path: string; constructor( @Inject(forwardRef(() => AppComponent)) private appComponent: AppComponent, + private dialog: MatDialog, private route: ActivatedRoute, - private httpService: HttpService + private httpService: HttpService, + private location: Location ) { } @@ -46,7 +50,26 @@ export class LoadSessionURLComponent { const f: any = new Blob([d], { type: "application/json" }); this.appComponent.loadSessionFile(f); } catch (e) { - alert("ERROR: session " + path + " does not exist"); + // display error dialog + await this.openErrorDialog(path); + // go to previous route + this.location.back(); } } -} + + private async openErrorDialog(path: string) { + const dialogRef = this.dialog.open( + DialogShowMessageComponent, + { + data: { + title: "Error!", + message: "Session " + path + " does not exist." + }, + disableClose: true + } + ); + + // wait for dialog to be closed + await dialogRef.afterClosed().toPromise(); + } +} \ No newline at end of file -- GitLab From 0fd803ff1bf5d61ce399c241aa1bc7ed283392c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr> Date: Fri, 29 Jul 2022 15:44:06 +0200 Subject: [PATCH 3/6] feat: load session by URL: add confirmation dialog in case of existing calculators refs #476 --- src/app/app.module.ts | 3 ++ ...og-confirm-load-session-url.component.html | 15 +++++++ ...alog-confirm-load-session-url.component.ts | 39 ++++++++++++++++++ .../load-session-url.component.ts | 41 ++++++++++++++++++- 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.html create mode 100644 src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 84271156c..28eac7d96 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -120,6 +120,7 @@ import { import { ImmediateErrorStateMatcher } from "./formulaire/immediate-error-state-matcher"; import { LoadSessionURLComponent } from "./components/load-session-url/load-session-url.component"; import { DialogShowMessageComponent } from "./components/dialog-show-message/dialog-show-message.component"; +import { DialogConfirmLoadSessionURLComponent } from "./components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component"; const appRoutes: Routes = [ { path: "list/search", component: CalculatorListComponent }, @@ -205,6 +206,7 @@ const appRoutes: Routes = [ DialogNewPbCloisonComponent, DialogLoadPredefinedEspeceComponent, DialogShowMessageComponent, + DialogConfirmLoadSessionURLComponent, FieldSetComponent, FieldsetContainerComponent, FixedResultsComponent, @@ -264,6 +266,7 @@ const appRoutes: Routes = [ DialogSaveSessionComponent, DialogNewPbCloisonComponent, DialogLoadSessionComponent, + DialogConfirmLoadSessionURLComponent, DialogLogEntriesDetailsComponent, DialogShowMessageComponent ], diff --git a/src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.html b/src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.html new file mode 100644 index 000000000..749838ad4 --- /dev/null +++ b/src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.html @@ -0,0 +1,15 @@ +<h1 mat-dialog-title [innerHTML]="uitextTitle"></h1> +<div> + <mat-checkbox [(ngModel)]="emptyCurrentSession"> + {{ uitextEmptyCurrentSession }} + </mat-checkbox> + + <div mat-dialog-actions [attr.align]="'end'"> + <button mat-raised-button color="primary" [mat-dialog-close]="false" cdkFocusInitial> + {{ uitextCancel }} + </button> + <button mat-raised-button type="submit" color="warn" (click)="loadSession()"> + {{ uitextLoad }} + </button> + </div> +</div> \ No newline at end of file diff --git a/src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.ts b/src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.ts new file mode 100644 index 000000000..b6e0497f9 --- /dev/null +++ b/src/app/components/dialog-confirm-load-session-url/dialog-confirm-load-session-url.component.ts @@ -0,0 +1,39 @@ +import { Component, Inject } from "@angular/core"; +import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; + +@Component({ + selector: "dialog-confirm-load-session-url", + templateUrl: "dialog-confirm-load-session-url.component.html", +}) +export class DialogConfirmLoadSessionURLComponent { + + public emptyCurrentSession: boolean = false; + + constructor( + public dialogRef: MatDialogRef<DialogConfirmLoadSessionURLComponent>, + @Inject(MAT_DIALOG_DATA) public data: any + ) { + } + + public loadSession() { + this.dialogRef.close({ + emptySession: this.emptyCurrentSession + }); + } + + public get uitextTitle() { + return "Please confirm loading"; + } + + public get uitextEmptyCurrentSession() { + return "Empty current session"; + } + + public get uitextCancel() { + return "Cancel"; + } + + public get uitextLoad() { + return "Load"; + } +} diff --git a/src/app/components/load-session-url/load-session-url.component.ts b/src/app/components/load-session-url/load-session-url.component.ts index 52e98eab9..d44aa286b 100644 --- a/src/app/components/load-session-url/load-session-url.component.ts +++ b/src/app/components/load-session-url/load-session-url.component.ts @@ -3,7 +3,9 @@ import { Component, forwardRef, Inject } from "@angular/core"; import { MatDialog } from "@angular/material/dialog"; import { ActivatedRoute } from "@angular/router"; import { AppComponent } from "app/app.component"; +import { FormulaireService } from "app/services/formulaire.service"; import { HttpService } from "app/services/http.service"; +import { DialogConfirmLoadSessionURLComponent } from "../dialog-confirm-load-session-url/dialog-confirm-load-session-url.component"; import { DialogShowMessageComponent } from "../dialog-show-message/dialog-show-message.component"; // load a session file by its URL (either local or remote) @@ -19,11 +21,33 @@ export class LoadSessionURLComponent { private dialog: MatDialog, private route: ActivatedRoute, private httpService: HttpService, - private location: Location + private location: Location, + private formulaireService: FormulaireService ) { } ngOnInit() { + // check open calculators + if (this.formulaireService.formulaires.length > 0) { + this.confirmLoadSession().then(emptySession => { + if (emptySession === undefined) { + // cancel has been clicked, go to previous route + this.location.back(); + } + else { + if (emptySession) { + this.appComponent.doEmptySession(); + } + this.loadSession(); + } + }); + } + else { + this.loadSession(); + } + } + + private loadSession() { // get "path" argument from URL const path = this.route.snapshot.params.path; @@ -72,4 +96,19 @@ export class LoadSessionURLComponent { // wait for dialog to be closed await dialogRef.afterClosed().toPromise(); } + + private confirmLoadSession(): Promise<boolean> { + const dialogRef = this.dialog.open( + DialogConfirmLoadSessionURLComponent, + { + data: { + }, + disableClose: true + } + ); + + return dialogRef.afterClosed().toPromise().then(result => { + return result.emptySession; + }); + } } \ No newline at end of file -- GitLab From a42600339638298dd0a30b72f1f8e3d4a17fea78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr> Date: Mon, 8 Aug 2022 12:00:33 +0200 Subject: [PATCH 4/6] feat: load remote session file by its URL refs #476 --- assets/scripts/gofetch.php | 57 +++++++++++++++++++ src/app/app.component.ts | 2 +- .../load-session-url.component.ts | 24 +++++++- src/app/services/formulaire.service.ts | 11 +++- 4 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 assets/scripts/gofetch.php diff --git a/assets/scripts/gofetch.php b/assets/scripts/gofetch.php new file mode 100644 index 000000000..846108572 --- /dev/null +++ b/assets/scripts/gofetch.php @@ -0,0 +1,57 @@ +<?php + +function do_log($msg) { + // echo($msg."\n"); +} + +do_log("start"); + +// http://localhost/gofetch.php?url=http%3A%2F%2Flocalhost%3A4200%2Fapp%2F%2Fexamples%2Fperr.json +// http://localhost/gofetch.php?url=https%3A%2F%2Fhydraulique.g-eau.fr%2Fcassiopee%2Fdevel%2Fapp%2F%2Fexamples%2Fperr.json + +// fonction str_ends_with si PHP < 8 +if ( ! function_exists( 'str_ends_with' ) ) { + function str_ends_with( $haystack, $needle ) { + if ( '' === $haystack && '' !== $needle ) { + return false; + } + $len = strlen( $needle ); + return 0 === substr_compare( $haystack, $needle, -$len, $len ); + } +} + +do_log("get url"); +$url = $_GET['url']; +do_log("url=" . $url); + +$url=urldecode($url); +do_log("decode url=" . $url); + +if( str_ends_with( strtolower( $url ), '.json' ) ) { + +do_log("curl init"); +// Initialise une session CURL. +$ch = curl_init(); + +do_log("setopt 1"); +// Récupère le contenu de la page +curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + +do_log("setopt 2"); +// Configure l'URL +curl_setopt($ch, CURLOPT_URL, $url); + +// Désactive la vérification du certificat si l'URL utilise HTTPS +if (strpos($url,'https')===0) { + do_log("setopt 3"); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); +} + +do_log("curl exec"); +// Exécute la requête +$result = curl_exec($ch); + +// Affiche le résultat +echo $result; +} +?> diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e005db3bc..05820087d 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -648,7 +648,7 @@ export class AppComponent implements OnInit, OnDestroy, Observer { }); } - public async loadSessionFile(f: File, info?: any) { + public async loadSessionFile(f: File|string, info?: any) { // notes merge detection: was there already some notes ? const existingNotes = Session.getInstance().documentation; // load diff --git a/src/app/components/load-session-url/load-session-url.component.ts b/src/app/components/load-session-url/load-session-url.component.ts index d44aa286b..6409e8142 100644 --- a/src/app/components/load-session-url/load-session-url.component.ts +++ b/src/app/components/load-session-url/load-session-url.component.ts @@ -53,6 +53,13 @@ export class LoadSessionURLComponent { if (path.startsWith("http")) { // general URL path + + // example URLs: + // http://localhost:4200/#/loadsession/https%3A%2F%2Fhydraulique.g-eau.fr%2Fcassiopee%2Fdevel%2Fapp%2Fexamples%2Fpab-complete-chain.json + // http://localhost/dist/#/loadsession/https%3A%2F%2Fhydraulique.g-eau.fr%2Fcassiopee%2Fdevel%2Fapp%2Fexamples%2Fpab-complete-chain.json + // turned by loadRemoteSession() to + // http://localhost/gofetch.php?url=https%3A%2F%2Fhydraulique.g-eau.fr%2Fcassiopee%2Fdevel%2Fapp%2F%2Fexamples%2Fpab-complete-chain.json + this.loadRemoteSession(path); } else { // local path @@ -64,6 +71,21 @@ export class LoadSessionURLComponent { } } + private async loadRemoteSession(path: string) { + try { + const url = "assets/scripts/gofetch.php?url=" + encodeURIComponent(path); + this.httpService.httpGetRequestPromise(url).then(resp => { + const s = JSON.stringify(resp); + this.appComponent.loadSessionFile(s); + }); + } catch (e) { + // display error dialog + await this.openErrorDialog(path); + // go to previous route + this.location.back(); + } + } + /** * load a locally stored session file * @param path local path in the form eg. app/examples/pab-complete-chain.json @@ -111,4 +133,4 @@ export class LoadSessionURLComponent { return result.emptySession; }); } -} \ No newline at end of file +} diff --git a/src/app/services/formulaire.service.ts b/src/app/services/formulaire.service.ts index bf650c62b..775e6ae0e 100644 --- a/src/app/services/formulaire.service.ts +++ b/src/app/services/formulaire.service.ts @@ -628,13 +628,18 @@ export class FormulaireService extends Observable { * @param f fichier session * @param formInfos infos sur les modules de calcul @see DialogLoadSessionComponent.calculators */ - public async loadSession(f: File, formInfos: any[] = []): Promise<{ hasErrors: boolean, loaded: string[] }> { + public async loadSession(i: Blob | string, formInfos: any[] = []): Promise<{ hasErrors: boolean, loaded: string[] }> { try { // disable "empty fields" flag temporarly const emptyFields = ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit; ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit = false; - const s = await this.readSingleFile(f); + let s; + if (i instanceof Blob) { + s = await this.readSingleFile(i as File); + } else { + s = i; + } const uids: string[] = []; formInfos.forEach((fi) => { if (fi.selected) { @@ -827,7 +832,7 @@ export class FormulaireService extends Observable { ) { const dependingNubs = Session.getInstance().getDependingNubs(f.currentNub.uid, symbol, forceResetAllDependencies, true); for (const dn of dependingNubs) { - if (! visited.includes(dn.uid)) { + if (!visited.includes(dn.uid)) { const form = this.getFormulaireFromNubId(dn.uid); if (form) { const hadResults = form.hasResults; -- GitLab From ccaaf0177d09f13c0e4f4889c1596c98441b179e Mon Sep 17 00:00:00 2001 From: David Dorchies <david.dorchies@inrae.fr> Date: Tue, 9 Aug 2022 09:33:25 +0000 Subject: [PATCH 5/6] feat: move PHP proxy in src/assets for auto-deployment Refs #476 --- jalhyd_branch | 2 +- {assets => src/assets}/scripts/gofetch.php | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {assets => src/assets}/scripts/gofetch.php (100%) diff --git a/jalhyd_branch b/jalhyd_branch index a9175be31..d64531f13 100644 --- a/jalhyd_branch +++ b/jalhyd_branch @@ -1 +1 @@ -318-mettre-a-jour-les-paquets-npm +devel diff --git a/assets/scripts/gofetch.php b/src/assets/scripts/gofetch.php similarity index 100% rename from assets/scripts/gofetch.php rename to src/assets/scripts/gofetch.php -- GitLab From 67aad088fe0259d0f88a8da449cb2a9c2e880c14 Mon Sep 17 00:00:00 2001 From: David Dorchies <david.dorchies@inrae.fr> Date: Tue, 9 Aug 2022 10:36:36 +0000 Subject: [PATCH 6/6] fix: set execution autorisation for gofetch.php Refs #476 --- src/assets/scripts/gofetch.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 src/assets/scripts/gofetch.php diff --git a/src/assets/scripts/gofetch.php b/src/assets/scripts/gofetch.php old mode 100644 new mode 100755 -- GitLab