This commit is contained in:
2026-06-02 17:39:58 +02:00
36 changed files with 2520 additions and 463 deletions
+39 -28
View File
@@ -8,6 +8,7 @@
@inject IJSRuntime JS
@inject SharepointToolbox.Web.Services.OAuth.IOAuthFlowCache OAuthCache
@inject SharepointToolbox.Web.Infrastructure.Persistence.ProfileRepository ProfileRepo
@inject TranslationSource T
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.WebUtilities
@using Microsoft.JSInterop
@@ -23,7 +24,7 @@
<span class="logo-mark">SP</span>
<span class="logo-text">SP Toolbox</span>
</div>
<button class="toggle-btn @(_sidebarCollapsed ? "collapsed" : "")" @onclick="ToggleSidebar" title="Toggle sidebar"></button>
<button class="toggle-btn @(_sidebarCollapsed ? "collapsed" : "")" @onclick="ToggleSidebar" title="@T["nav.toggleSidebar"]"></button>
</div>
@* User identity badge *@
@@ -44,7 +45,7 @@
<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>
@onclick="ReconnectAsync">@T["nav.reconnect"]</button>
</div>
}
</div>
@@ -61,11 +62,11 @@
<div class="nav-search">
<span class="nav-icon">🔍</span>
<input type="text" class="nav-search-input" placeholder="Search…"
<input type="text" class="nav-search-input" placeholder="@T["nav.searchPlaceholder"]"
@bind="_navFilter" @bind:event="oninput" />
@if (!string.IsNullOrEmpty(_navFilter))
{
<button class="nav-search-clear" @onclick="ClearFilter" title="Clear">✕</button>
<button class="nav-search-clear" @onclick="ClearFilter" title="@T["nav.clear"]">✕</button>
}
</div>
@@ -81,16 +82,16 @@
lastSection = item.Section;
if (!string.IsNullOrEmpty(item.Section))
{
<div class="nav-divider">@item.Section</div>
<div class="nav-divider">@T[item.Section]</div>
}
}
<NavLink href="@item.Href" Match="@(item.Href == "/" ? NavLinkMatch.All : NavLinkMatch.Prefix)" class="nav-item">
<span class="nav-icon">@item.Icon</span><span class="nav-label">@item.Label</span>
<span class="nav-icon">@item.Icon</span><span class="nav-label">@T[item.Label]</span>
</NavLink>
}
@if (items.Count == 0)
{
<div class="nav-empty">No match</div>
<div class="nav-empty">@T["nav.noMatch"]</div>
}
</nav>
@@ -98,13 +99,13 @@
<AuthorizeView>
<Authorized>
<a href="/account/logout" class="nav-item">
<span class="nav-icon">🚪</span><span class="nav-label">Logout</span>
<span class="nav-icon">🚪</span><span class="nav-label">@T["nav.logout"]</span>
</a>
</Authorized>
</AuthorizeView>
<button class="nav-item theme-toggle" @onclick="ToggleTheme">
<span class="nav-icon">🌙</span>
<span class="nav-label">@(_dark ? "Light Mode" : "Dark Mode")</span>
<span class="nav-label">@(_dark ? T["nav.lightMode"] : T["nav.darkMode"])</span>
<span class="switch @(_dark ? "on" : "")"></span>
</button>
</div>
@@ -120,7 +121,7 @@
}
else
{
<div style="padding:2rem;color:var(--text-muted)">Loading</div>
<div style="padding:2rem;color:var(--text-muted)">@T["nav.loading"]</div>
}
</main>
</div>
@@ -137,23 +138,23 @@
private static readonly NavItem[] AllNavItems =
{
new("/", "🏠", "Home", "", "always"),
new("/permissions", "🔐", "Permissions", "", "profile"),
new("/storage", "💾", "Storage", "", "profile"),
new("/duplicates", "📋", "Duplicates", "", "profile"),
new("/versions", "🗂️", "Version Cleanup", "", "profile"),
new("/transfer", "📦", "File Transfer", "", "profile"),
new("/bulk-members", "👥", "Bulk Members", "Bulk", "profile"),
new("/bulk-sites", "🌐", "Bulk Sites", "Bulk", "profile"),
new("/folder-structure", "📁", "Folder Structure", "Bulk", "profile"),
new("/user-audit", "👤", "User Access Audit","Audit", "profile"),
new("/user-directory", "📖", "User Directory", "Audit", "profile"),
new("/templates", "📐", "Templates", "Config", "profile"),
new("/profiles", "⚙️", "Client Profiles", "Admin", "admin"),
new("/admin/users", "👥", "User Management", "Admin", "admin"),
new("/admin/audit", "📋", "Audit Logs", "Admin", "admin"),
new("/settings", "🔧", "Settings", "", "always"),
new("/account/change-password","🔑", "Change Password", "", "auth"),
new("/", "🏠", "nav.home", "", "always"),
new("/permissions", "🔐", "tab.permissions", "", "profile"),
new("/storage", "💾", "tab.storage", "", "profile"),
new("/duplicates", "📋", "tab.duplicates", "", "profile"),
new("/versions", "🗂️", "versions.tab", "", "profile"),
new("/transfer", "📦", "nav.fileTransfer", "", "profile"),
new("/bulk-members", "👥", "tab.bulkMembers", "nav.section.bulk", "profile"),
new("/bulk-sites", "🌐", "tab.bulkSites", "nav.section.bulk", "profile"),
new("/folder-structure", "📁", "tab.folderStructure", "nav.section.bulk", "profile"),
new("/user-audit", "👤", "tab.userAccessAudit", "nav.section.audit", "profile"),
new("/user-directory", "📖", "nav.userDirectory", "nav.section.audit", "profile"),
new("/templates", "📐", "tab.templates", "nav.section.config", "profile"),
new("/profiles", "⚙️", "nav.clientProfiles", "nav.section.admin", "admin"),
new("/admin/users", "👥", "nav.userManagement", "nav.section.admin", "admin"),
new("/admin/audit", "📋", "nav.auditLogs", "nav.section.admin", "admin"),
new("/settings", "🔧", "tab.settings", "", "always"),
new("/account/change-password","🔑", "nav.changePassword", "", "auth"),
};
private IEnumerable<NavItem> VisibleNavItems()
@@ -168,7 +169,7 @@
_ => true
})
.Where(i => filter.Length == 0
|| i.Label.Contains(filter, StringComparison.OrdinalIgnoreCase));
|| T[i.Label].Contains(filter, StringComparison.OrdinalIgnoreCase));
}
private void ClearFilter() => _navFilter = string.Empty;
@@ -177,6 +178,16 @@
protected override void OnInitialized()
{
// Apply the user's language to this circuit's TranslationSource (used by every page in
// the circuit) and to the ambient culture (used by the export services). Runs in both
// the prerender and the interactive circuit, before any page renders — so the whole app
// is in the right language and stays there across SPA navigation.
var lang = Session.Settings.Lang;
T.SetCulture(lang);
var culture = TranslationSource.Resolve(lang);
System.Globalization.CultureInfo.CurrentCulture = culture;
System.Globalization.CultureInfo.CurrentUICulture = culture;
Session.ProfileChanged += OnProfileChanged;
UserContext.Initialized += OnUserContextInitialized;
_dark = string.Equals(Session.Settings.Theme, "Dark", StringComparison.OrdinalIgnoreCase);