|
| 1 | +@page "/dashboard" |
| 2 | +@attribute [UAuthAuthorize] |
| 3 | +@inherits UAuthPageBase |
| 4 | +@inject IUAuthClient UAuthClient |
| 5 | +@inject IUAuthClientProductInfoProvider ProductInfoProvider |
| 6 | +@inject ISnackbar Snackbar |
| 7 | + |
| 8 | +<PageTitle>Dashboard · Identity Lab</PageTitle> |
| 9 | +<main class="dashboard-shell"> |
| 10 | + <aside class="side-nav"> |
| 11 | + <div><span class="lab-icon">⚗</span><strong>Identity Lab</strong><small>Playground Environment</small></div> |
| 12 | + <nav><a class="selected">⌂ Dashboard</a><a>♙ My Identity</a><a>▱ Device Chains</a><a>♢ Authorization</a></nav> |
| 13 | + <div class="side-bottom"><a href="https://ultimateauth.com">Documentation ↗</a><a href="https://github.com/CodeBeamOrg/UltimateAuth">GitHub ↗</a><small>A CodeBeam Product</small></div> |
| 14 | + </aside> |
| 15 | + <section class="dashboard-content"> |
| 16 | + <header class="dash-heading"><div><span>DASHBOARD</span><h1>Welcome back, @DisplayName.</h1><p>This is your isolated playground environment for exploring authentication,<br />authorization and session management with UltimateAuth.</p></div><MudChip T="string" Color="Color.Success" Variant="Variant.Outlined">● Playground Environment</MudChip></header> |
| 17 | + <div class="metric-grid"> |
| 18 | + <article><i>♙</i><div><b>Current user</b><strong>@UserName</strong><small>UltimateAuth identity</small></div></article> |
| 19 | + <article><i>▱</i><div><b>Device chains</b><strong>@(chains?.TotalCount.ToString() ?? "—")</strong><small>From GetMyChainsAsync</small></div></article> |
| 20 | + <article><i>✓</i><div><b>Session state</b><strong>@(AuthState?.Identity?.SessionState?.ToString() ?? "—")</strong><small>Server-controlled</small></div></article> |
| 21 | + <article><i>♢</i><div><b>Authorization</b><strong>@(AuthState?.Claims.Roles.Count ?? 0)</strong><small>Assigned roles</small></div></article> |
| 22 | + </div> |
| 23 | + <div class="dashboard-grid"> |
| 24 | + <MudPaper Class="dash-panel chains-panel" Elevation="0"> |
| 25 | + <div class="panel-title"><h2>Your device chains</h2><MudButton Variant="Variant.Text" OnClick="LoadChains">Refresh</MudButton></div> |
| 26 | + @if (loading) { <MudProgressLinear Indeterminate="true" Color="Color.Primary" /> } |
| 27 | + else if (chains?.Items.Count > 0) |
| 28 | + { |
| 29 | + <MudTable Items="chains.Items" Dense="true" Hover="true" Elevation="0"> |
| 30 | + <HeaderContent><MudTh>Device / client</MudTh><MudTh>Last seen</MudTh><MudTh>State</MudTh><MudTh></MudTh></HeaderContent> |
| 31 | + <RowTemplate> |
| 32 | + <MudTd><b>@DeviceName(context)</b><br /><small>@(context.IsCurrentDevice ? "Current device" : context.Platform)</small></MudTd> |
| 33 | + <MudTd>@FormatDate(context.LastSeenAt)</MudTd> |
| 34 | + <MudTd><MudChip T="string" Size="Size.Small" Color="@(context.IsRevoked ? Color.Error : Color.Success)">@context.State</MudChip></MudTd> |
| 35 | + <MudTd><MudButton Size="Size.Small" Color="Color.Error" Variant="Variant.Text" Disabled="context.IsRevoked" OnClick="() => Revoke(context)">Revoke</MudButton></MudTd> |
| 36 | + </RowTemplate> |
| 37 | + </MudTable> |
| 38 | + } |
| 39 | + else { <div class="empty-state"><b>No device chains returned</b><p>UltimateAuth has not exposed an active chain for this account yet.</p></div> } |
| 40 | + </MudPaper> |
| 41 | + <MudPaper Class="dash-panel" Elevation="0"> |
| 42 | + <div class="panel-title"><h2>Authentication state</h2></div> |
| 43 | + <dl class="info-list"><dt>Authenticated</dt><dd>@(AuthState?.IsAuthenticated == true ? "Yes" : "No")</dd><dt>Tenant</dt><dd>@AuthState?.Identity?.Tenant.Value</dd><dt>User key</dt><dd class="truncate">@AuthState?.Identity?.UserKey.Value</dd><dt>Roles</dt><dd>@Roles</dd><dt>Client profile</dt><dd>@product?.ClientProfile</dd></dl> |
| 44 | + </MudPaper> |
| 45 | + <MudPaper Class="dash-panel" Elevation="0"> |
| 46 | + <div class="panel-title"><h2>Quick actions</h2></div> |
| 47 | + <div class="quick-actions"><MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Refresh" OnClick="Validate">Validate session</MudButton><MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Devices" OnClick="LoadChains">Explore chains</MudButton><MudButton Variant="Variant.Outlined" Color="Color.Error" StartIcon="@Icons.Material.Filled.Logout" OnClick="Logout">Sign out</MudButton></div> |
| 48 | + </MudPaper> |
| 49 | + <MudPaper Class="dash-panel" Elevation="0"> |
| 50 | + <div class="panel-title"><h2>Playground info</h2></div> |
| 51 | + <dl class="info-list"><dt>Environment</dt><dd><span class="green-dot">●</span> Isolated</dd><dt>Storage</dt><dd>In-memory</dd><dt>Started</dt><dd>@product?.StartedAt.ToLocalTime().ToString("g")</dd><dt>UltimateAuth</dt><dd>v@product?.Version</dd></dl> |
| 52 | + </MudPaper> |
| 53 | + </div> |
| 54 | + </section> |
| 55 | +</main> |
| 56 | + |
| 57 | +@code { |
| 58 | + private PagedResult<SessionChainSummary>? chains; |
| 59 | + private UAuthClientProductInfo? product; |
| 60 | + private bool loading; |
| 61 | + private string DisplayName => AuthState?.Identity?.DisplayName ?? AuthState?.Identity?.PrimaryUserName ?? "Explorer"; |
| 62 | + private string UserName => AuthState?.Identity?.PrimaryUserName ?? "—"; |
| 63 | + private string Roles => AuthState is null || AuthState.Claims.Roles.Count == 0 ? "None" : string.Join(", ", AuthState.Claims.Roles); |
| 64 | + |
| 65 | + protected override async Task OnInitializedAsync() |
| 66 | + { |
| 67 | + product = ProductInfoProvider.Get(); |
| 68 | + if (AuthState.IsAuthenticated) |
| 69 | + await LoadChains(); |
| 70 | + } |
| 71 | + |
| 72 | + protected override void HandleAuthStateChanged(UAuthStateChangeReason reason) |
| 73 | + { |
| 74 | + if (reason == UAuthStateChangeReason.Authenticated && chains is null) |
| 75 | + _ = InvokeAsync(async () => { await LoadChains(); StateHasChanged(); }); |
| 76 | + } |
| 77 | + private async Task LoadChains() { loading = true; var result = await UAuthClient.Sessions.GetMyChainsAsync(); chains = result.Value; loading = false; } |
| 78 | + private async Task Revoke(SessionChainSummary chain) { var result = await UAuthClient.Sessions.RevokeMyChainAsync(chain.ChainId); Snackbar.Add(result.IsSuccess ? "Device chain revoked." : result.ErrorText ?? "Revocation failed.", result.IsSuccess ? Severity.Success : Severity.Error); await LoadChains(); } |
| 79 | + private async Task Validate() { var result = await UAuthClient.Flows.ValidateAsync(); Snackbar.Add(result.IsValid ? "Session is valid." : $"Session state: {result.State}", result.IsValid ? Severity.Success : Severity.Warning); } |
| 80 | + private Task Logout() => UAuthClient.Flows.LogoutAsync(); |
| 81 | + private static string DeviceName(SessionChainSummary chain) => string.Join(" · ", new[] { chain.Browser, chain.OperatingSystem, chain.DeviceType }.Where(x => !string.IsNullOrWhiteSpace(x))); |
| 82 | + private static string FormatDate(DateTimeOffset? value) => value?.ToLocalTime().ToString("g") ?? "Not reported"; |
| 83 | +} |
0 commit comments