From 67f422539db69f51defc2e3e7a2d67d5868dfb01 Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Sun, 3 May 2026 09:05:40 +0200 Subject: [PATCH] =?UTF-8?q?v0.12.0=20=E2=80=94=20Report=20tool=20auto-depl?= =?UTF-8?q?oy=20from=20PROSERVE=20ZIP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each PROSERVE ZIP can ship a _report/ subfolder alongside _migrations/. After install + DB migration, the launcher copies _report/ to the local XAMPP htdocs (default C:\xampp\htdocs\ProserveReport\) so the Reports tab in the sidebar always serves the matching version. Service (PSLauncher.Core/ReportTool) - IReportToolDeployer + ReportToolDeployer. - Atomic deploy: copy to {target}.new/ → rename current to .old/ → rename .new → final → cleanup .old. Apache never sees a half-state. - Skip silencieux si _report/ absent du ZIP (PROSERVE release qui ne touche pas au Report). - DeployStatus enum (SkippedNoSourceFolder, XamppNotFound, Failed, Deployed) renvoyé pour gestion UI claire. Config (LocalConfig) - ReportToolConfig (HtdocsRoot, FolderName, AutoDeploy). Defaults matchent l'install standard ASTERION (C:\xampp\htdocs\ProserveReport). Install pipeline (MainViewModel) - Étape 6 après extraction + migrations. Progress reporté via le footer Library : « 📂 Déploiement Report : 47/120 — assets/main.js ». XAMPP introuvable → dialog avec lien vers Settings → Avancés. Settings UI (SettingsDialog → Avancés) - Nouveau bloc « OUTIL REPORT (XAMPP htdocs) » : 2 champs path + checkbox AutoDeploy + bouton « 📂 Re-déployer maintenant » qui rejoue le deploy sur la dernière version installée. Utile après changement de chemin htdocs ou si l'auto-deploy avait foiré (XAMPP éteint). Versions bumped to 0.12.0 (App + Updater + installer .iss). Côté release : tu ajoutes _report/ dans le source de PROSERVE, tu zippes, tu uploades. Les clients récupèrent ZIP + migrations + Report en une seule install. Co-Authored-By: Claude Opus 4.7 (1M context) --- installer/PSLauncher.iss | 2 +- src/PSLauncher.App/App.xaml.cs | 8 ++ src/PSLauncher.App/PSLauncher.App.csproj | 6 +- .../ViewModels/MainViewModel.cs | 48 ++++++++ .../ViewModels/SettingsViewModel.cs | 66 +++++++++++ src/PSLauncher.App/Views/SettingsDialog.xaml | 50 ++++++++ src/PSLauncher.Core/Localization/Strings.cs | 43 +++++++ .../ReportTool/IReportToolDeployer.cs | 47 ++++++++ .../ReportTool/ReportToolDeployer.cs | 107 ++++++++++++++++++ src/PSLauncher.Models/LocalConfig.cs | 22 ++++ .../PSLauncher.Updater.csproj | 2 +- 11 files changed, 396 insertions(+), 5 deletions(-) create mode 100644 src/PSLauncher.Core/ReportTool/IReportToolDeployer.cs create mode 100644 src/PSLauncher.Core/ReportTool/ReportToolDeployer.cs diff --git a/installer/PSLauncher.iss b/installer/PSLauncher.iss index a98dcdc..4e9d4d9 100644 --- a/installer/PSLauncher.iss +++ b/installer/PSLauncher.iss @@ -11,7 +11,7 @@ #define MyAppName "PROSERVE Launcher" #define MyAppShortName "PSLauncher" -#define MyAppVersion "0.11.0" +#define MyAppVersion "0.12.0" #define MyAppPublisher "ASTERION VR" #define MyAppURL "https://asterionvr.com" #define MyAppExeName "PSLauncher.exe" diff --git a/src/PSLauncher.App/App.xaml.cs b/src/PSLauncher.App/App.xaml.cs index 5a8f3e6..24ee7d6 100644 --- a/src/PSLauncher.App/App.xaml.cs +++ b/src/PSLauncher.App/App.xaml.cs @@ -18,6 +18,7 @@ using PSLauncher.Core.Localization; using PSLauncher.Core.Manifests; using PSLauncher.Core.Migrations; using PSLauncher.Core.Process; +using PSLauncher.Core.ReportTool; using PSLauncher.Core.Updates; using PSLauncher.Models; using Serilog; @@ -174,6 +175,13 @@ public partial class App : Application sp.GetRequiredService().Database.EncryptedPassword), sp.GetRequiredService>())); + // Déploiement de l'outil Report (page web XAMPP) à l'install d'une + // nouvelle version : copie atomique de _report/ → C:\xampp\htdocs\ProserveReport\ + services.AddSingleton(sp => + new ReportToolDeployer( + configProvider: () => sp.GetRequiredService().ReportTool, + sp.GetRequiredService>())); + services.AddTransient(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/PSLauncher.App/PSLauncher.App.csproj b/src/PSLauncher.App/PSLauncher.App.csproj index 750aa69..6fb5fc6 100644 --- a/src/PSLauncher.App/PSLauncher.App.csproj +++ b/src/PSLauncher.App/PSLauncher.App.csproj @@ -15,9 +15,9 @@ PROSERVE Launcher © 2026 ASTERION VR — All rights reserved PSLauncher.App - 0.11.0 - 0.11.0.0 - 0.11.0.0 + 0.12.0 + 0.12.0.0 + 0.12.0.0 true diff --git a/src/PSLauncher.App/ViewModels/MainViewModel.cs b/src/PSLauncher.App/ViewModels/MainViewModel.cs index 3f24bb4..291654e 100644 --- a/src/PSLauncher.App/ViewModels/MainViewModel.cs +++ b/src/PSLauncher.App/ViewModels/MainViewModel.cs @@ -16,6 +16,7 @@ using PSLauncher.Core.Localization; using PSLauncher.Core.Manifests; using PSLauncher.Core.Migrations; using PSLauncher.Core.Process; +using PSLauncher.Core.ReportTool; using PSLauncher.Core.Updates; using PSLauncher.Models; @@ -41,6 +42,7 @@ public sealed partial class MainViewModel : ObservableObject private readonly ILauncherSelfUpdater _selfUpdater; private readonly IToastService _toastService; private readonly IDatabaseMigrationService _migrationService; + private readonly IReportToolDeployer _reportDeployer; private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; @@ -223,6 +225,7 @@ public sealed partial class MainViewModel : ObservableObject ILauncherSelfUpdater selfUpdater, IToastService toastService, IDatabaseMigrationService migrationService, + IReportToolDeployer reportDeployer, IServiceProvider serviceProvider, ILogger logger) { @@ -238,6 +241,7 @@ public sealed partial class MainViewModel : ObservableObject _selfUpdater = selfUpdater; _toastService = toastService; _migrationService = migrationService; + _reportDeployer = reportDeployer; _serviceProvider = serviceProvider; _logger = logger; @@ -745,6 +749,50 @@ public sealed partial class MainViewModel : ObservableObject } } + // 6) Déploiement de l'outil Report (page web XAMPP). + // Cherche _report/ dans le ZIP extrait et copie vers htdocs en + // atomic rename. Skip silencieux si _report/ absent du ZIP. + if (_config.ReportTool.AutoDeploy) + { + StatusMessage = Strings.StatusDeployingReport(row.Version); + ProgressDetail = null; + ProgressPercent = 0; + var rdProgress = new Progress(dp => + { + if (dp.FilesTotal > 0) + { + var pct = (double)dp.FilesDone / dp.FilesTotal * 100.0; + ProgressPercent = pct; + row.ProgressPercent = pct; + } + var label = Strings.ProgressReportDeploy(dp.FilesDone, dp.FilesTotal, dp.CurrentFilename); + ProgressDetail = label; + row.ProgressDetail = label; + }); + var rdResult = await _reportDeployer.DeployAsync(target, rdProgress, ct); + switch (rdResult.Status) + { + case DeployStatus.Deployed: + _logger.LogInformation("Report tool deployed ({N} files) for v{Version}", rdResult.FilesDeployed, row.Version); + break; + case DeployStatus.SkippedNoSourceFolder: + _logger.LogInformation("No _report/ in v{Version} ZIP, report tool unchanged", row.Version); + break; + case DeployStatus.XamppNotFound: + MessageBox.Show( + Strings.MsgReportXamppNotFound(_config.ReportTool.HtdocsRoot), + Strings.MsgBoxReportDeployFailed, + MessageBoxButton.OK, MessageBoxImage.Warning); + break; + case DeployStatus.Failed: + MessageBox.Show( + Strings.MsgReportDeployFailed(rdResult.ErrorMessage ?? "?"), + Strings.MsgBoxReportDeployFailed, + MessageBoxButton.OK, MessageBoxImage.Warning); + break; + } + } + StatusMessage = Strings.StatusInstalledSuccess(row.Version); ProgressDetail = null; ProgressPercent = 0; diff --git a/src/PSLauncher.App/ViewModels/SettingsViewModel.cs b/src/PSLauncher.App/ViewModels/SettingsViewModel.cs index 1444269..d14c23b 100644 --- a/src/PSLauncher.App/ViewModels/SettingsViewModel.cs +++ b/src/PSLauncher.App/ViewModels/SettingsViewModel.cs @@ -12,6 +12,7 @@ using PSLauncher.Core.Installations; using PSLauncher.Core.Licensing; using PSLauncher.Core.Localization; using PSLauncher.Core.Migrations; +using PSLauncher.Core.ReportTool; using PSLauncher.Models; namespace PSLauncher.App.ViewModels; @@ -30,6 +31,7 @@ public sealed partial class SettingsViewModel : ObservableObject private readonly ILicenseService _licenseService; private readonly IDownloadStateStore _downloadStore; private readonly IDatabaseMigrationService _migrationService; + private readonly IReportToolDeployer _reportDeployer; private readonly IInstallationRegistry _registry; private readonly HttpClient _http; private readonly ILogger _logger; @@ -57,6 +59,13 @@ public sealed partial class SettingsViewModel : ObservableObject [ObservableProperty] private string? _migrationsReplayStatus; [ObservableProperty] private bool _isReplayingMigrations; + // ----- Report Tool deploy settings ----- + [ObservableProperty] private string _reportHtdocsRoot; + [ObservableProperty] private string _reportFolderName; + [ObservableProperty] private bool _reportAutoDeploy; + [ObservableProperty] private string? _reportDeployStatus; + [ObservableProperty] private bool _isDeployingReport; + public IReadOnlyList AvailableLanguages { get; } = PSLauncher.Core.Localization.Strings.Available .Select(t => new LanguageOption(t.Code, t.Name)) @@ -124,6 +133,7 @@ public sealed partial class SettingsViewModel : ObservableObject ILicenseService licenseService, IDownloadStateStore downloadStore, IDatabaseMigrationService migrationService, + IReportToolDeployer reportDeployer, IInstallationRegistry registry, HttpClient http, ILogger logger) @@ -133,6 +143,7 @@ public sealed partial class SettingsViewModel : ObservableObject _licenseService = licenseService; _downloadStore = downloadStore; _migrationService = migrationService; + _reportDeployer = reportDeployer; _registry = registry; _http = http; _logger = logger; @@ -150,6 +161,10 @@ public sealed partial class SettingsViewModel : ObservableObject _dbName = config.Database.Database; _dbAutoApplyMigrations = config.Database.AutoApplyMigrations; + _reportHtdocsRoot = config.ReportTool.HtdocsRoot; + _reportFolderName = config.ReportTool.FolderName; + _reportAutoDeploy = config.ReportTool.AutoDeploy; + LauncherVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "0.0.0"; MachineId = licenseService.GetMachineId(); LogsDirectory = App.LogsDirectory; @@ -269,6 +284,10 @@ public sealed partial class SettingsViewModel : ObservableObject _config.Database.Database = DbName.Trim(); _config.Database.AutoApplyMigrations = DbAutoApplyMigrations; + _config.ReportTool.HtdocsRoot = ReportHtdocsRoot.Trim(); + _config.ReportTool.FolderName = ReportFolderName.Trim(); + _config.ReportTool.AutoDeploy = ReportAutoDeploy; + _configStore.Save(_config); _logger.LogInformation("Settings saved (language={Lang}, changed={Changed})", _config.Language, languageChanged); @@ -334,6 +353,53 @@ public sealed partial class SettingsViewModel : ObservableObject private static string FormatSize(long bytes) => Strings.FormatSize(bytes); + /// + /// Re-déploie l'outil Report depuis la dernière version PROSERVE installée + /// vers htdocs. Utile si le déploiement post-install avait échoué (XAMPP + /// déplacé, htdocs absent), ou pour resync après changement du chemin htdocs + /// dans Settings. + /// + [RelayCommand] + private async Task RedeployReportAsync() + { + var installed = _registry.Scan().OrderByDescending(v => v.Version).FirstOrDefault(); + if (installed is null) + { + ReportDeployStatus = "Aucune version installée — installe d'abord une version qui contient _report/."; + return; + } + IsDeployingReport = true; + ReportDeployStatus = $"Déploiement depuis v{installed.Version}…"; + try + { + // Persiste les éventuels changements de chemin avant le deploy + Save(); + var progress = new Progress(dp => + { + if (dp.FilesTotal > 0) + ReportDeployStatus = $"{dp.FilesDone}/{dp.FilesTotal} : {dp.CurrentFilename}"; + }); + var result = await _reportDeployer.DeployAsync(installed.FolderPath, progress, CancellationToken.None); + ReportDeployStatus = result.Status switch + { + DeployStatus.Deployed => $"✓ {result.FilesDeployed} fichiers déployés vers {result.TargetPath}", + DeployStatus.SkippedNoSourceFolder => $"⚠ La version v{installed.Version} ne contient pas de _report/ — rien à déployer", + DeployStatus.XamppNotFound => $"✗ {result.ErrorMessage}", + DeployStatus.Failed => $"✗ {result.ErrorMessage}", + _ => "?", + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Redeploy report failed"); + ReportDeployStatus = $"✗ {ex.Message}"; + } + finally + { + IsDeployingReport = false; + } + } + // ===================== DB MIGRATIONS ===================== /// diff --git a/src/PSLauncher.App/Views/SettingsDialog.xaml b/src/PSLauncher.App/Views/SettingsDialog.xaml index 2975549..6c5967c 100644 --- a/src/PSLauncher.App/Views/SettingsDialog.xaml +++ b/src/PSLauncher.App/Views/SettingsDialog.xaml @@ -413,6 +413,56 @@ + + + + + + + + + + + + + + + + + + + +