Initial commit
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
@inject AuthenticationStateProvider AuthProvider
|
||||
@inject IUserService UserService
|
||||
@inject IUserContextAccessor UserContext
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using SharepointToolbox.Web.Services.Auth
|
||||
@using SharepointToolbox.Web.Services.Session
|
||||
|
||||
@* Invisible component. Run once per circuit to seed IUserContextAccessor. *@
|
||||
|
||||
@code {
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var state = await AuthProvider.GetAuthenticationStateAsync();
|
||||
var principal = state.User;
|
||||
if (principal.Identity?.IsAuthenticated != true) return;
|
||||
|
||||
var email = principal.FindFirst("preferred_username")?.Value
|
||||
?? principal.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value;
|
||||
if (string.IsNullOrEmpty(email)) return;
|
||||
|
||||
var user = await UserService.GetByEmailAsync(email);
|
||||
if (user is not null) UserContext.Initialize(user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="no-profile">
|
||||
<h2>No profile selected</h2>
|
||||
<p>Select or create a tenant profile to get started.</p>
|
||||
<a href="/profiles" class="btn btn-primary">Go to Profiles</a>
|
||||
</div>
|
||||
@@ -0,0 +1,29 @@
|
||||
@if (IsRunning || !string.IsNullOrEmpty(StatusMessage))
|
||||
{
|
||||
<div class="progress-panel mt-8">
|
||||
@if (!string.IsNullOrEmpty(StatusMessage))
|
||||
{
|
||||
<div class="progress-msg">@StatusMessage</div>
|
||||
}
|
||||
@if (IsRunning)
|
||||
{
|
||||
<div class="progress-bar">
|
||||
@if (Total > 0)
|
||||
{
|
||||
<div class="progress-fill" style="width:@(Math.Round((double)Current/Total*100))%"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="progress-fill indeterminate"></div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public bool IsRunning { get; set; }
|
||||
[Parameter] public string StatusMessage { get; set; } = string.Empty;
|
||||
[Parameter] public int Current { get; set; }
|
||||
[Parameter] public int Total { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
@inject ISessionCredentialStore CredStore
|
||||
@inject IUserSessionService Session
|
||||
@inject ISessionManager SessionManager
|
||||
@inject NavigationManager Nav
|
||||
@using SharepointToolbox.Web.Core.Models
|
||||
@using SharepointToolbox.Web.Services.Session
|
||||
|
||||
@if (_visible)
|
||||
{
|
||||
<div class="modal-overlay">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-header">
|
||||
<h3>Connect to Microsoft</h3>
|
||||
<p class="text-muted">
|
||||
Authenticate to access <strong>@Session.CurrentProfile?.Name</strong>.
|
||||
Your session token is stored in your browser only — never saved to disk.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(_error))
|
||||
{
|
||||
<div class="alert alert-error">@_error</div>
|
||||
}
|
||||
|
||||
<div class="flex-row mt-8">
|
||||
<button class="btn btn-primary" @onclick="ConnectAsync" disabled="@_connecting">
|
||||
@(_connecting ? "Redirecting…" : "Connect via Microsoft")
|
||||
</button>
|
||||
<button class="btn btn-secondary" @onclick="Cancel" disabled="@_connecting">Cancel</button>
|
||||
</div>
|
||||
|
||||
<p class="text-muted" style="font-size:11px;margin-top:8px">
|
||||
You will be redirected to Microsoft login. MFA is supported.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public EventCallback OnConnected { get; set; }
|
||||
|
||||
private bool _visible;
|
||||
private bool _connecting;
|
||||
private string _error = string.Empty;
|
||||
|
||||
public async Task ShowAsync()
|
||||
{
|
||||
_error = string.Empty;
|
||||
_connecting = false;
|
||||
_visible = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task ConnectAsync()
|
||||
{
|
||||
var profile = Session.CurrentProfile;
|
||||
if (profile is null) { _error = "No client profile selected."; return; }
|
||||
|
||||
_connecting = true;
|
||||
_error = string.Empty;
|
||||
|
||||
// Clear any stale CSOM contexts
|
||||
await SessionManager.ClearAllAsync();
|
||||
|
||||
var currentUrl = Nav.Uri;
|
||||
var connectUrl = $"/connect/initiate?profileId={Uri.EscapeDataString(profile.Id)}" +
|
||||
$"&returnUrl={Uri.EscapeDataString(currentUrl)}";
|
||||
|
||||
// Force full HTTP navigation to break out of the Blazor SignalR circuit
|
||||
Nav.NavigateTo(connectUrl, forceLoad: true);
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
_visible = false;
|
||||
_connecting = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
@inject IUserContextAccessor UserContext
|
||||
@using SharepointToolbox.Web.Core.Models
|
||||
@using SharepointToolbox.Web.Services.Session
|
||||
|
||||
@* Wrap write-only UI. TechN0 sees the ReadOnlyContent fallback. *@
|
||||
|
||||
@if (UserContext.Role >= UserRole.TechN1)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (ReadOnlyContent is not null)
|
||||
{
|
||||
@ReadOnlyContent
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
You have <strong>read-only</strong> access (Tech-N0). Contact an Admin to request write access.
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
[Parameter] public RenderFragment? ReadOnlyContent { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user