Update README.md
This commit is contained in:
@@ -37,8 +37,4 @@ Application pour administrer, auditer et exporter des donnees depuis des sites S
|
||||
|
||||
## Architecture
|
||||
|
||||
<<<<<<< HEAD
|
||||
MVVM (CommunityToolkit) · DI via Microsoft.Extensions.Hosting · Authentification MSAL avec cache persistant et broker WAM · Microsoft Graph SDK · PnP.Framework (CSOM) · Localisation .resx (EN/FR) · Branding configurable dans les exports HTML.
|
||||
=======
|
||||
MVVM (CommunityToolkit) · DI via Microsoft.Extensions.Hosting · Authentification MSAL avec cache persistant et broker WAM · Microsoft Graph SDK · PnP.Framework (CSOM) · Localisation .resx (EN/FR) · Branding configurable dans les exports HTML.
|
||||
>>>>>>> bbfb9097ce51d03430cb030531418f8b944e9a76
|
||||
|
||||
@@ -34,6 +34,19 @@ public partial class App : Application
|
||||
.Build();
|
||||
|
||||
host.Start();
|
||||
|
||||
// Apply persisted language before any UI is created so bindings resolve to the saved culture.
|
||||
try
|
||||
{
|
||||
var settings = host.Services.GetRequiredService<SettingsService>().GetSettingsAsync().GetAwaiter().GetResult();
|
||||
if (!string.IsNullOrWhiteSpace(settings.Lang))
|
||||
Localization.TranslationSource.Instance.CurrentCulture = new System.Globalization.CultureInfo(settings.Lang);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Failed to apply persisted language at startup");
|
||||
}
|
||||
|
||||
App app = new();
|
||||
app.InitializeComponent();
|
||||
|
||||
|
||||
@@ -124,6 +124,9 @@
|
||||
<data name="profile.clientid" xml:space="preserve">
|
||||
<value>ID client</value>
|
||||
</data>
|
||||
<data name="profile.clientid.hint" xml:space="preserve">
|
||||
<value>Optionnel — laissez vide pour enregistrer l'application automatiquement</value>
|
||||
</data>
|
||||
<data name="profile.add" xml:space="preserve">
|
||||
<value>Ajouter</value>
|
||||
</data>
|
||||
|
||||
@@ -124,6 +124,9 @@
|
||||
<data name="profile.clientid" xml:space="preserve">
|
||||
<value>Client ID</value>
|
||||
</data>
|
||||
<data name="profile.clientid.hint" xml:space="preserve">
|
||||
<value>Optional — leave blank to register the app automatically</value>
|
||||
</data>
|
||||
<data name="profile.add" xml:space="preserve">
|
||||
<value>Add</value>
|
||||
</data>
|
||||
|
||||
@@ -24,8 +24,9 @@ public class ProfileService
|
||||
!Uri.TryCreate(profile.TenantUrl, UriKind.Absolute, out _))
|
||||
throw new ArgumentException("TenantUrl must be a valid absolute URL.", nameof(profile));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(profile.ClientId))
|
||||
throw new ArgumentException("ClientId must not be empty.", nameof(profile));
|
||||
// ClientId is optional at creation time: the user can register the app from within
|
||||
// the tool, which will populate ClientId/AppId on the profile afterwards.
|
||||
profile.ClientId ??= string.Empty;
|
||||
|
||||
var existing = (await _repository.LoadAsync()).ToList();
|
||||
existing.Add(profile);
|
||||
|
||||
@@ -19,6 +19,11 @@ public partial class ProfileManagementViewModel : ObservableObject
|
||||
private readonly ILogger<ProfileManagementViewModel> _logger;
|
||||
private readonly IAppRegistrationService _appRegistrationService;
|
||||
|
||||
// Well-known public client (Microsoft Graph Command Line Tools) used as a bootstrap
|
||||
// when a profile has no ClientId yet, so the user can sign in as admin and have the
|
||||
// app registration created for them.
|
||||
private const string BootstrapClientId = "14d82eec-204b-4c2f-b7e8-296a70dab67e";
|
||||
|
||||
[ObservableProperty]
|
||||
private TenantProfile? _selectedProfile;
|
||||
|
||||
@@ -137,7 +142,7 @@ public partial class ProfileManagementViewModel : ObservableObject
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(NewName)) return false;
|
||||
if (!Uri.TryCreate(NewTenantUrl, UriKind.Absolute, out _)) return false;
|
||||
if (string.IsNullOrWhiteSpace(NewClientId)) return false;
|
||||
// ClientId is optional — leaving it blank lets the user register the app from within the tool.
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -150,7 +155,7 @@ public partial class ProfileManagementViewModel : ObservableObject
|
||||
{
|
||||
Name = NewName.Trim(),
|
||||
TenantUrl = NewTenantUrl.Trim(),
|
||||
ClientId = NewClientId.Trim()
|
||||
ClientId = NewClientId?.Trim() ?? string.Empty
|
||||
};
|
||||
await _profileService.AddProfileAsync(profile);
|
||||
Profiles.Add(profile);
|
||||
@@ -299,7 +304,14 @@ public partial class ProfileManagementViewModel : ObservableObject
|
||||
RegistrationStatus = TranslationSource.Instance["profile.register.checking"];
|
||||
try
|
||||
{
|
||||
var isAdmin = await _appRegistrationService.IsGlobalAdminAsync(SelectedProfile.ClientId, ct);
|
||||
// Use the profile's own ClientId if it has one; otherwise bootstrap with the
|
||||
// Microsoft Graph Command Line Tools public client so a first-time profile
|
||||
// (name + URL only) can still perform the admin check and registration.
|
||||
var authClientId = string.IsNullOrWhiteSpace(SelectedProfile.ClientId)
|
||||
? BootstrapClientId
|
||||
: SelectedProfile.ClientId;
|
||||
|
||||
var isAdmin = await _appRegistrationService.IsGlobalAdminAsync(authClientId, ct);
|
||||
if (!isAdmin)
|
||||
{
|
||||
ShowFallbackInstructions = true;
|
||||
@@ -308,11 +320,15 @@ public partial class ProfileManagementViewModel : ObservableObject
|
||||
}
|
||||
|
||||
RegistrationStatus = TranslationSource.Instance["profile.register.registering"];
|
||||
var result = await _appRegistrationService.RegisterAsync(SelectedProfile.ClientId, SelectedProfile.Name, ct);
|
||||
var result = await _appRegistrationService.RegisterAsync(authClientId, SelectedProfile.Name, ct);
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
SelectedProfile.AppId = result.AppId;
|
||||
// If the profile had no ClientId, adopt the freshly registered app's id
|
||||
// so subsequent sign-ins use the profile's own app registration.
|
||||
if (string.IsNullOrWhiteSpace(SelectedProfile.ClientId))
|
||||
SelectedProfile.ClientId = result.AppId!;
|
||||
await _profileService.UpdateProfileAsync(SelectedProfile);
|
||||
RegistrationStatus = TranslationSource.Instance["profile.register.success"];
|
||||
OnPropertyChanged(nameof(HasRegisteredApp));
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.name]}"
|
||||
Grid.Row="0" Grid.Column="0" />
|
||||
@@ -48,6 +49,9 @@
|
||||
Grid.Row="2" Grid.Column="0" />
|
||||
<TextBox Text="{Binding NewClientId, UpdateSourceTrigger=PropertyChanged}"
|
||||
Grid.Row="2" Grid.Column="1" Margin="0,2" />
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Margin="0,2,0,0"
|
||||
FontSize="11" FontStyle="Italic" Foreground="#666666" TextWrapping="Wrap"
|
||||
Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.clientid.hint]}" />
|
||||
</Grid>
|
||||
|
||||
<!-- Client Logo -->
|
||||
|
||||
Reference in New Issue
Block a user