Initial commit
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
@inherits LayoutComponentBase
|
||||
@inject IUserSessionService Session
|
||||
@inject IUserContextAccessor UserContext
|
||||
@inject ISessionCredentialStore CredStore
|
||||
@inject ISessionManager SessionManager
|
||||
@inject NavigationManager Nav
|
||||
@inject SharepointToolbox.Web.Services.OAuth.IOAuthFlowCache OAuthCache
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using Microsoft.AspNetCore.WebUtilities
|
||||
@using SharepointToolbox.Web.Core.Models
|
||||
@using SharepointToolbox.Web.Services.Session
|
||||
|
||||
<AppInitializer />
|
||||
|
||||
<div class="app-layout">
|
||||
<aside class="sidebar @(_sidebarCollapsed ? "collapsed" : "")">
|
||||
<div class="sidebar-header">
|
||||
<span class="logo-text">SP Toolbox</span>
|
||||
<button class="toggle-btn" @onclick="ToggleSidebar">☰</button>
|
||||
</div>
|
||||
|
||||
@* User identity badge *@
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<div class="profile-badge" style="background:var(--surface-2);border-radius:6px;margin:8px;padding:8px">
|
||||
<div style="font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
|
||||
@UserContext.DisplayName
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
|
||||
@UserContext.Email
|
||||
</div>
|
||||
<div style="margin-top:4px">
|
||||
<span class="chip @RoleChipClass(UserContext.Role)" style="font-size:10px">@UserContext.Role</span>
|
||||
</div>
|
||||
@if (_hasCredentials)
|
||||
{
|
||||
<div style="font-size:10px;color:var(--text-muted);margin-top:4px">
|
||||
SP: @_credUsername
|
||||
<button class="btn btn-secondary btn-sm" style="padding:2px 6px;font-size:10px;margin-left:4px"
|
||||
@onclick="ReconnectAsync">Reconnect</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
@if (Session.HasProfile)
|
||||
{
|
||||
<div class="profile-badge">
|
||||
<span class="profile-icon">🏢</span>
|
||||
<span class="profile-name">@Session.CurrentProfile!.Name</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<nav class="nav-menu">
|
||||
<NavLink href="/" Match="NavLinkMatch.All" class="nav-item">
|
||||
<span class="nav-icon">🏠</span><span class="nav-label">Home</span>
|
||||
</NavLink>
|
||||
@if (Session.HasProfile)
|
||||
{
|
||||
<NavLink href="/permissions" class="nav-item">
|
||||
<span class="nav-icon">🔐</span><span class="nav-label">Permissions</span>
|
||||
</NavLink>
|
||||
<NavLink href="/storage" class="nav-item">
|
||||
<span class="nav-icon">💾</span><span class="nav-label">Storage</span>
|
||||
</NavLink>
|
||||
<NavLink href="/search" class="nav-item">
|
||||
<span class="nav-icon">🔍</span><span class="nav-label">Search</span>
|
||||
</NavLink>
|
||||
<NavLink href="/duplicates" class="nav-item">
|
||||
<span class="nav-icon">📋</span><span class="nav-label">Duplicates</span>
|
||||
</NavLink>
|
||||
<NavLink href="/versions" class="nav-item">
|
||||
<span class="nav-icon">🗂️</span><span class="nav-label">Version Cleanup</span>
|
||||
</NavLink>
|
||||
<NavLink href="/transfer" class="nav-item">
|
||||
<span class="nav-icon">📦</span><span class="nav-label">File Transfer</span>
|
||||
</NavLink>
|
||||
<div class="nav-divider">Bulk</div>
|
||||
<NavLink href="/bulk-members" class="nav-item">
|
||||
<span class="nav-icon">👥</span><span class="nav-label">Bulk Members</span>
|
||||
</NavLink>
|
||||
<NavLink href="/bulk-sites" class="nav-item">
|
||||
<span class="nav-icon">🌐</span><span class="nav-label">Bulk Sites</span>
|
||||
</NavLink>
|
||||
<NavLink href="/folder-structure" class="nav-item">
|
||||
<span class="nav-icon">📁</span><span class="nav-label">Folder Structure</span>
|
||||
</NavLink>
|
||||
<div class="nav-divider">Audit</div>
|
||||
<NavLink href="/user-audit" class="nav-item">
|
||||
<span class="nav-icon">👤</span><span class="nav-label">User Access Audit</span>
|
||||
</NavLink>
|
||||
<NavLink href="/user-directory" class="nav-item">
|
||||
<span class="nav-icon">📖</span><span class="nav-label">User Directory</span>
|
||||
</NavLink>
|
||||
<div class="nav-divider">Config</div>
|
||||
<NavLink href="/templates" class="nav-item">
|
||||
<span class="nav-icon">📐</span><span class="nav-label">Templates</span>
|
||||
</NavLink>
|
||||
}
|
||||
|
||||
@* Admin-only section *@
|
||||
@if (UserContext.Role == UserRole.Admin)
|
||||
{
|
||||
<div class="nav-divider">Admin</div>
|
||||
<NavLink href="/profiles" class="nav-item">
|
||||
<span class="nav-icon">⚙️</span><span class="nav-label">Client Profiles</span>
|
||||
</NavLink>
|
||||
<NavLink href="/admin/users" class="nav-item">
|
||||
<span class="nav-icon">👥</span><span class="nav-label">User Management</span>
|
||||
</NavLink>
|
||||
<NavLink href="/admin/audit" class="nav-item">
|
||||
<span class="nav-icon">📋</span><span class="nav-label">Audit Logs</span>
|
||||
</NavLink>
|
||||
}
|
||||
|
||||
<NavLink href="/settings" class="nav-item">
|
||||
<span class="nav-icon">🔧</span><span class="nav-label">Settings</span>
|
||||
</NavLink>
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<a href="/account/logout" class="nav-item" style="color:var(--text-muted)">
|
||||
<span class="nav-icon">🚪</span><span class="nav-label">Logout</span>
|
||||
</a>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="content">
|
||||
@if (UserContext.IsAuthenticated)
|
||||
{
|
||||
@Body
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="padding:2rem;color:var(--text-muted)">Loading…</div>
|
||||
}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<SessionCredentialsModal @ref="_credModal" OnConnected="OnCredentialsConnected" />
|
||||
|
||||
@code {
|
||||
private bool _sidebarCollapsed;
|
||||
private bool _hasCredentials;
|
||||
private string _credUsername = string.Empty;
|
||||
private SessionCredentialsModal? _credModal;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Session.ProfileChanged += OnProfileChanged;
|
||||
UserContext.Initialized += OnUserContextInitialized;
|
||||
}
|
||||
|
||||
private void OnUserContextInitialized() => InvokeAsync(StateHasChanged);
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!firstRender) return;
|
||||
|
||||
// Pick up token_key from OAuth callback redirect before checking credential state
|
||||
await HandleOAuthCallbackAsync();
|
||||
await RefreshCredentialState();
|
||||
|
||||
// Check for connect_error query param
|
||||
var uri = new Uri(Nav.Uri);
|
||||
var query = QueryHelpers.ParseQuery(uri.Query);
|
||||
if (query.TryGetValue("connect_error", out var err) && !string.IsNullOrEmpty(err))
|
||||
{
|
||||
Nav.NavigateTo(uri.GetLeftPart(UriPartial.Path), replace: true);
|
||||
if (_credModal is not null)
|
||||
{
|
||||
await _credModal.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
// If profile selected but no credentials → show modal
|
||||
if (Session.HasProfile && !_hasCredentials && _credModal is not null)
|
||||
await _credModal.ShowAsync();
|
||||
}
|
||||
|
||||
private async Task HandleOAuthCallbackAsync()
|
||||
{
|
||||
var uri = new Uri(Nav.Uri);
|
||||
var query = QueryHelpers.ParseQuery(uri.Query);
|
||||
|
||||
if (!query.TryGetValue("token_key", out var tokenKey) || string.IsNullOrEmpty(tokenKey))
|
||||
return;
|
||||
|
||||
var tokens = OAuthCache.GetAndRemoveTokens(tokenKey!);
|
||||
if (tokens is not null)
|
||||
{
|
||||
await CredStore.SetAsync(tokens);
|
||||
await SessionManager.ClearAllAsync();
|
||||
}
|
||||
|
||||
// Strip token_key from URL bar
|
||||
Nav.NavigateTo(uri.GetLeftPart(UriPartial.Path), replace: true);
|
||||
}
|
||||
|
||||
private async Task RefreshCredentialState()
|
||||
{
|
||||
var tokens = await CredStore.GetAsync();
|
||||
_hasCredentials = tokens is not null && !string.IsNullOrEmpty(tokens.RefreshToken);
|
||||
_credUsername = tokens?.UserPrincipalName ?? string.Empty;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task ReconnectAsync()
|
||||
{
|
||||
await CredStore.ClearAsync();
|
||||
await SessionManager.ClearAllAsync();
|
||||
_hasCredentials = false;
|
||||
_credUsername = string.Empty;
|
||||
if (Session.HasProfile && _credModal is not null)
|
||||
await _credModal.ShowAsync();
|
||||
}
|
||||
|
||||
private async Task OnCredentialsConnected()
|
||||
{
|
||||
await RefreshCredentialState();
|
||||
}
|
||||
|
||||
private void OnProfileChanged()
|
||||
{
|
||||
InvokeAsync(async () =>
|
||||
{
|
||||
StateHasChanged();
|
||||
// New profile selected → prompt for credentials if none
|
||||
if (Session.HasProfile && !_hasCredentials && _credModal is not null)
|
||||
await _credModal.ShowAsync();
|
||||
});
|
||||
}
|
||||
|
||||
private void ToggleSidebar() => _sidebarCollapsed = !_sidebarCollapsed;
|
||||
|
||||
private static string RoleChipClass(UserRole role) => role switch
|
||||
{
|
||||
UserRole.Admin => "chip-red",
|
||||
UserRole.TechN1 => "chip-green",
|
||||
_ => "chip-blue"
|
||||
};
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Session.ProfileChanged -= OnProfileChanged;
|
||||
UserContext.Initialized -= OnUserContextInitialized;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user