Utilise le menu du site pour te connecter. La zone d'administration
devient disponible des qu'un compte avec le role `admin` est actif.
}
else if (!IsAdmin)
{
Acces refuse
Ce compte n'a pas les droits d'administration
La gestion des utilisateurs est reservee aux comptes portant le role `admin`.
}
else if (IsLoadingUsers)
{
Chargement
Recuperation de la liste des utilisateurs
Le site rassemble les comptes Keycloak et les profils du site.
}
else if (!string.IsNullOrWhiteSpace(LoadError))
{
Serveur
Impossible de charger l'administration
@LoadError
}
else
{
Utilisateurs
Parcourir les comptes
@if (FilteredUsers.Count == 0)
{
Recherche
Aucun utilisateur ne correspond au filtre en cours.
}
else
{
@foreach (var user in FilteredUsers)
{
}
}
@if (IsLoadingDetail)
{
Chargement
Recuperation de la fiche utilisateur
Les details du compte sont en cours de chargement.
}
else if (!string.IsNullOrWhiteSpace(DetailError))
{
Serveur
Impossible de charger cette fiche
@DetailError
}
else if (SelectedUser is null)
{
Selection
Choisis un utilisateur
La fiche detaillee apparait ici des qu'un compte est selectionne.
}
else
{
Edition
@SelectedUser.DisplayName
Les roles restent geres dans Keycloak. Cette page couvre l'etat du compte et le profil du site.
Identite Keycloak@SelectedUser.IdentityDisplayNameCompte cree le@FormatDate(SelectedUser.AccountCreatedUtc)Profil site cree le@FormatDate(SelectedUser.SiteProfileCreatedUtc)Profil site mis a jour@FormatDate(SelectedUser.SiteProfileUpdatedUtc)
@if (!string.IsNullOrWhiteSpace(SaveError))
{
@SaveError
}
@if (!string.IsNullOrWhiteSpace(SaveMessage))
{
@SaveMessage
}
Le profil site est cree automatiquement lors du premier enregistrement.
}
}
@code {
private readonly AdminUserFormModel Form = new();
private readonly List Users = [];
private AdminUserDetailResponse? SelectedUser;
private string? SelectedSubject;
private string SearchTerm = string.Empty;
private bool IsAuthenticated;
private bool IsAdmin;
private bool IsLoadingUsers = true;
private bool IsLoadingDetail;
private bool IsSaving;
private string? LoadError;
private string? DetailError;
private string? SaveError;
private string? SaveMessage;
private List FilteredUsers
=> Users
.Where(MatchesSearch)
.ToList();
private string HeroStatusTitle
=> !IsAuthenticated
? "Connexion requise"
: !IsAdmin
? "Compte non admin"
: IsLoadingUsers
? "Chargement des utilisateurs"
: $"{Users.Count} comptes disponibles";
private string HeroStatusDescription
=> !IsAuthenticated
? "Connecte-toi avec un compte admin pour ouvrir la zone d'administration."
: !IsAdmin
? "Le compte actuel est authentifie mais ne dispose pas du role admin."
: IsLoadingUsers
? "Le site recupere les comptes et les profils deja relies a MySQL."
: "La fiche detaillee permet de modifier les informations de compte et le profil du site.";
protected override async Task OnInitializedAsync()
{
AuthenticationStateProvider.AuthenticationStateChanged += HandleAuthenticationStateChanged;
await LoadUsersAsync();
}
private void HandleAuthenticationStateChanged(Task authenticationStateTask)
=> _ = InvokeAsync(LoadUsersAsync);
private async Task LoadUsersAsync()
{
LoadError = null;
DetailError = null;
SaveError = null;
SaveMessage = null;
IsLoadingUsers = true;
try
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity?.IsAuthenticated != true)
{
ResetAdminState();
return;
}
IsAuthenticated = true;
IsAdmin = authState.User.IsInRole("admin");
if (!IsAdmin)
{
Users.Clear();
SelectedSubject = null;
SelectedUser = null;
Form.Reset();
return;
}
var response = await Http.GetAsync("api/admin/users");
if (!response.IsSuccessStatusCode)
{
LoadError = await ReadErrorAsync(response, "La liste des utilisateurs n'a pas pu etre chargee.");
Users.Clear();
SelectedSubject = null;
SelectedUser = null;
Form.Reset();
return;
}
var users = await response.Content.ReadFromJsonAsync>();
Users.Clear();
Users.AddRange(users ?? []);
if (Users.Count == 0)
{
SelectedSubject = null;
SelectedUser = null;
Form.Reset();
return;
}
var nextSubject = Users.Any(user => user.Subject == SelectedSubject)
? SelectedSubject
: Users[0].Subject;
if (!string.IsNullOrWhiteSpace(nextSubject))
{
await LoadUserDetailAsync(nextSubject, keepFeedback: true);
}
}
catch (HttpRequestException)
{
LoadError = "Le service d'administration est temporairement indisponible.";
Users.Clear();
}
catch (TaskCanceledException)
{
LoadError = "Le chargement de l'administration a pris trop de temps.";
Users.Clear();
}
finally
{
IsLoadingUsers = false;
StateHasChanged();
}
}
private async Task SelectUserAsync(string subject)
{
if (string.Equals(subject, SelectedSubject, StringComparison.Ordinal))
{
return;
}
SaveError = null;
SaveMessage = null;
await LoadUserDetailAsync(subject, keepFeedback: false);
}
private async Task LoadUserDetailAsync(string subject, bool keepFeedback)
{
if (!keepFeedback)
{
SaveError = null;
SaveMessage = null;
}
IsLoadingDetail = true;
DetailError = null;
SelectedSubject = subject;
try
{
var response = await Http.GetAsync($"api/admin/users/{Uri.EscapeDataString(subject)}");
if (!response.IsSuccessStatusCode)
{
DetailError = await ReadErrorAsync(response, "La fiche utilisateur n'a pas pu etre chargee.");
SelectedUser = null;
return;
}
SelectedUser = await response.Content.ReadFromJsonAsync();
if (SelectedUser is null)
{
DetailError = "Le serveur a retourne une fiche vide.";
return;
}
FillForm(SelectedUser);
}
catch (HttpRequestException)
{
DetailError = "Le detail utilisateur est temporairement indisponible.";
SelectedUser = null;
}
catch (TaskCanceledException)
{
DetailError = "Le detail utilisateur a pris trop de temps a se charger.";
SelectedUser = null;
}
finally
{
IsLoadingDetail = false;
StateHasChanged();
}
}
private async Task SaveUserAsync()
{
if (IsSaving || SelectedSubject is null)
{
return;
}
IsSaving = true;
SaveError = null;
SaveMessage = null;
try
{
var payload = new AdminUpdateUserRequest
{
Username = Form.Username ?? string.Empty,
Email = Form.Email,
FirstName = Form.FirstName,
LastName = Form.LastName,
IsEnabled = Form.IsEnabled,
IsEmailVerified = Form.IsEmailVerified,
DisplayName = Form.DisplayName,
Club = Form.Club,
City = Form.City,
PreferredFormat = Form.PreferredFormat,
FavoriteCube = Form.FavoriteCube,
Bio = Form.Bio,
};
var response = await Http.PutAsJsonAsync($"api/admin/users/{Uri.EscapeDataString(SelectedSubject)}", payload);
if (!response.IsSuccessStatusCode)
{
SaveError = await ReadErrorAsync(response, "La mise a jour de l'utilisateur a echoue.");
return;
}
SelectedUser = await response.Content.ReadFromJsonAsync();
if (SelectedUser is null)
{
SaveError = "Le serveur a retourne une fiche vide apres la sauvegarde.";
return;
}
FillForm(SelectedUser);
UpdateSummary(SelectedUser);
SaveMessage = "La fiche utilisateur a bien ete mise a jour.";
}
catch (HttpRequestException)
{
SaveError = "Le service d'administration est temporairement indisponible.";
}
catch (TaskCanceledException)
{
SaveError = "La sauvegarde a pris trop de temps.";
}
finally
{
IsSaving = false;
}
}
private void UpdateSummary(AdminUserDetailResponse detail)
{
var summary = Users.FirstOrDefault(user => user.Subject == detail.Subject);
if (summary is null)
{
return;
}
summary.Username = detail.Username;
summary.Email = detail.Email;
summary.IdentityDisplayName = detail.IdentityDisplayName;
summary.SiteDisplayName = detail.DisplayName;
summary.IsEnabled = detail.IsEnabled;
summary.IsEmailVerified = detail.IsEmailVerified;
summary.HasSiteProfile = detail.HasSiteProfile;
summary.Club = detail.Club;
summary.City = detail.City;
summary.PreferredFormat = detail.PreferredFormat;
summary.AccountCreatedUtc = detail.AccountCreatedUtc;
summary.SiteProfileUpdatedUtc = detail.SiteProfileUpdatedUtc;
}
private void FillForm(AdminUserDetailResponse user)
{
Form.Username = user.Username;
Form.Email = user.Email;
Form.FirstName = user.FirstName;
Form.LastName = user.LastName;
Form.IsEnabled = user.IsEnabled;
Form.IsEmailVerified = user.IsEmailVerified;
Form.DisplayName = user.DisplayName;
Form.Club = user.Club;
Form.City = user.City;
Form.PreferredFormat = user.PreferredFormat;
Form.FavoriteCube = user.FavoriteCube;
Form.Bio = user.Bio;
}
private void ResetAdminState()
{
IsAuthenticated = false;
IsAdmin = false;
Users.Clear();
SelectedSubject = null;
SelectedUser = null;
Form.Reset();
}
private bool MatchesSearch(AdminUserSummaryResponse user)
{
var search = SearchTerm.Trim();
if (string.IsNullOrWhiteSpace(search))
{
return true;
}
return Contains(user.Username, search)
|| Contains(user.Email, search)
|| Contains(user.IdentityDisplayName, search)
|| Contains(user.SiteDisplayName, search)
|| Contains(user.Club, search)
|| Contains(user.City, search);
}
private static bool Contains(string? value, string search)
=> value?.Contains(search, StringComparison.OrdinalIgnoreCase) == true;
private static string BuildListDisplayName(AdminUserSummaryResponse user)
=> user.SiteDisplayName
?? user.IdentityDisplayName
?? user.Username;
private static string BuildUserCardFootnote(AdminUserSummaryResponse user)
=> user.SiteProfileUpdatedUtc is not null
? $"Maj site {FormatDate(user.SiteProfileUpdatedUtc)}"
: user.AccountCreatedUtc is not null
? $"Compte cree {FormatDate(user.AccountCreatedUtc)}"
: "Aucune date disponible";
private static async Task ReadErrorAsync(HttpResponseMessage response, string fallbackMessage)
{
try
{
var error = await response.Content.ReadFromJsonAsync();
if (!string.IsNullOrWhiteSpace(error?.Message))
{
return error.Message;
}
}
catch
{
}
return response.StatusCode switch
{
HttpStatusCode.Unauthorized => "La session a expire. Reconnecte-toi puis recharge la page.",
HttpStatusCode.Forbidden => "Ce compte n'a pas le role admin requis pour cette action.",
HttpStatusCode.NotFound => "Cet utilisateur n'existe plus ou n'est plus disponible.",
_ => fallbackMessage,
};
}
private static string FormatDate(DateTime? value)
=> value is null
? "Non disponible"
: value.Value.ToLocalTime().ToString("dd MMM yyyy 'a' HH:mm", CultureInfo.GetCultureInfo("fr-FR"));
public void Dispose()
=> AuthenticationStateProvider.AuthenticationStateChanged -= HandleAuthenticationStateChanged;
private sealed class ApiErrorMessage
{
public string? Message { get; set; }
}
private sealed class AdminUserFormModel
{
[Required(ErrorMessage = "Le nom d'utilisateur est obligatoire.")]
[MaxLength(120, ErrorMessage = "Le nom d'utilisateur doit rester sous 120 caracteres.")]
public string? Username { get; set; }
[MaxLength(255, ErrorMessage = "L'email doit rester sous 255 caracteres.")]
[EmailAddress(ErrorMessage = "L'email n'est pas valide.")]
public string? Email { get; set; }
[MaxLength(120, ErrorMessage = "Le prenom doit rester sous 120 caracteres.")]
public string? FirstName { get; set; }
[MaxLength(120, ErrorMessage = "Le nom doit rester sous 120 caracteres.")]
public string? LastName { get; set; }
public bool IsEnabled { get; set; } = true;
public bool IsEmailVerified { get; set; }
[MaxLength(120, ErrorMessage = "Le nom affiche doit rester sous 120 caracteres.")]
public string? DisplayName { get; set; }
[MaxLength(120, ErrorMessage = "Le club doit rester sous 120 caracteres.")]
public string? Club { get; set; }
[MaxLength(120, ErrorMessage = "La ville doit rester sous 120 caracteres.")]
public string? City { get; set; }
[MaxLength(40, ErrorMessage = "Le format prefere doit rester sous 40 caracteres.")]
public string? PreferredFormat { get; set; }
[MaxLength(120, ErrorMessage = "Le cube favori doit rester sous 120 caracteres.")]
public string? FavoriteCube { get; set; }
[MaxLength(1200, ErrorMessage = "La bio doit rester sous 1200 caracteres.")]
public string? Bio { get; set; }
public void Reset()
{
Username = string.Empty;
Email = string.Empty;
FirstName = string.Empty;
LastName = string.Empty;
IsEnabled = true;
IsEmailVerified = false;
DisplayName = string.Empty;
Club = string.Empty;
City = string.Empty;
PreferredFormat = string.Empty;
FavoriteCube = string.Empty;
Bio = string.Empty;
}
}
}