Added max list size circumvention for file transfers between sites.
This commit is contained in:
@@ -54,6 +54,14 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
[ObservableProperty]
|
||||
private bool _hideSystemGroupRaw = true;
|
||||
|
||||
/// <summary>When true, sharing link entries (SharingLinkType != null) are removed from results and exports.</summary>
|
||||
[ObservableProperty]
|
||||
private bool _excludeSharingLinks;
|
||||
|
||||
/// <summary>When true, "Limited Access System Group For Web/List" entries are removed from results and exports.</summary>
|
||||
[ObservableProperty]
|
||||
private bool _excludeSystemGroups;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _includeSubsites;
|
||||
|
||||
@@ -102,6 +110,17 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
private ReportSplitMode CurrentSplit => SplitModeIndex == 1 ? ReportSplitMode.BySite : ReportSplitMode.Single;
|
||||
private HtmlSplitLayout CurrentLayout => HtmlLayoutIndex == 1 ? HtmlSplitLayout.SingleTabbed : HtmlSplitLayout.SeparateFiles;
|
||||
|
||||
/// <summary>
|
||||
/// Results after applying ExcludeSharingLinks / ExcludeSystemGroups filters.
|
||||
/// Rebuilt when Results changes or filter flags change.
|
||||
/// </summary>
|
||||
private IReadOnlyList<PermissionEntry> _filteredResults = Array.Empty<PermissionEntry>();
|
||||
public IReadOnlyList<PermissionEntry> FilteredResults
|
||||
{
|
||||
get => _filteredResults;
|
||||
private set => SetProperty(ref _filteredResults, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simplified wrappers computed from Results. Rebuilt when Results changes.
|
||||
/// </summary>
|
||||
@@ -124,16 +143,37 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
|
||||
/// <summary>
|
||||
/// The collection the DataGrid actually binds to. Returns:
|
||||
/// - Results (raw) when simplified mode is OFF
|
||||
/// - FilteredResults (raw) when simplified mode is OFF
|
||||
/// - SimplifiedResults when simplified mode is ON and detail view is ON
|
||||
/// - (View handles summary display separately via Summaries property)
|
||||
/// </summary>
|
||||
public object ActiveItemsSource => IsSimplifiedMode
|
||||
? (object)SimplifiedResults
|
||||
: Results;
|
||||
: FilteredResults;
|
||||
|
||||
partial void OnFolderDepthChanged(int value) => OnPropertyChanged(nameof(IsMaxDepth));
|
||||
|
||||
partial void OnExcludeSharingLinksChanged(bool value) => RefreshAfterFilterChange();
|
||||
partial void OnExcludeSystemGroupsChanged(bool value) => RefreshAfterFilterChange();
|
||||
|
||||
private void RefreshAfterFilterChange()
|
||||
{
|
||||
if (Results.Count == 0) return;
|
||||
RebuildFilteredResults();
|
||||
if (IsSimplifiedMode) RebuildSimplifiedData();
|
||||
OnPropertyChanged(nameof(ActiveItemsSource));
|
||||
}
|
||||
|
||||
private void RebuildFilteredResults()
|
||||
{
|
||||
IEnumerable<PermissionEntry> filtered = Results;
|
||||
if (ExcludeSharingLinks)
|
||||
filtered = filtered.Where(e => string.IsNullOrEmpty(e.SharingLinkType));
|
||||
if (ExcludeSystemGroups)
|
||||
filtered = filtered.Where(e => !e.GrantedThrough.Contains("Limited Access System Group", StringComparison.OrdinalIgnoreCase));
|
||||
FilteredResults = filtered.ToList();
|
||||
}
|
||||
|
||||
// ── Commands ────────────────────────────────────────────────────────────
|
||||
|
||||
public IAsyncRelayCommand ExportCsvCommand { get; }
|
||||
@@ -172,8 +212,8 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
_settingsService = settingsService;
|
||||
_ownershipService = ownershipService;
|
||||
|
||||
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport);
|
||||
ExportHtmlCommand = new AsyncRelayCommand(ExportHtmlAsync, CanExport);
|
||||
ExportCsvCommand = new AsyncRelayCommand(ct => ExportCsvAsync(ct), CanExport);
|
||||
ExportHtmlCommand = new AsyncRelayCommand(ct => ExportHtmlAsync(ct), CanExport);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -199,8 +239,8 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
_settingsService = settingsService;
|
||||
_ownershipService = ownershipService;
|
||||
|
||||
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport);
|
||||
ExportHtmlCommand = new AsyncRelayCommand(ExportHtmlAsync, CanExport);
|
||||
ExportCsvCommand = new AsyncRelayCommand(ct => ExportCsvAsync(ct), CanExport);
|
||||
ExportHtmlCommand = new AsyncRelayCommand(ct => ExportHtmlAsync(ct), CanExport);
|
||||
}
|
||||
|
||||
// ── FeatureViewModelBase implementation ─────────────────────────────────
|
||||
@@ -221,9 +261,18 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
/// Recomputes SimplifiedResults and Summaries from the current Results collection.
|
||||
/// Called when Results changes or when simplified mode is toggled on.
|
||||
/// </summary>
|
||||
private static bool IsSimplifiedModeNoise(PermissionEntry e)
|
||||
{
|
||||
if (e.Users.Contains("SharePointHome", StringComparison.OrdinalIgnoreCase)) return true;
|
||||
if (e.GrantedThrough.Contains("SharePointHome", StringComparison.OrdinalIgnoreCase)) return true;
|
||||
if (e.UserLogins.Split(';').Any(l => l.Trim().StartsWith("c:0u.c|tenant|", StringComparison.OrdinalIgnoreCase))) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RebuildSimplifiedData()
|
||||
{
|
||||
SimplifiedResults = SimplifiedPermissionEntry.WrapAll(Results);
|
||||
var forSimplified = FilteredResults.Where(e => !IsSimplifiedModeNoise(e));
|
||||
SimplifiedResults = SimplifiedPermissionEntry.WrapAll(forSimplified);
|
||||
Summaries = PermissionSummaryBuilder.Build(SimplifiedResults);
|
||||
}
|
||||
|
||||
@@ -303,6 +352,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
await dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
Results = new ObservableCollection<PermissionEntry>(allEntries);
|
||||
RebuildFilteredResults();
|
||||
if (IsSimplifiedMode)
|
||||
RebuildSimplifiedData();
|
||||
OnPropertyChanged(nameof(ActiveItemsSource));
|
||||
@@ -311,6 +361,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
else
|
||||
{
|
||||
Results = new ObservableCollection<PermissionEntry>(allEntries);
|
||||
RebuildFilteredResults();
|
||||
if (IsSimplifiedMode)
|
||||
RebuildSimplifiedData();
|
||||
OnPropertyChanged(nameof(ActiveItemsSource));
|
||||
@@ -384,6 +435,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
{
|
||||
_currentProfile = profile;
|
||||
Results = new ObservableCollection<PermissionEntry>();
|
||||
FilteredResults = Array.Empty<PermissionEntry>();
|
||||
SimplifiedResults = Array.Empty<SimplifiedPermissionEntry>();
|
||||
Summaries = Array.Empty<PermissionSummary>();
|
||||
OnPropertyChanged(nameof(ActiveItemsSource));
|
||||
@@ -404,7 +456,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
|
||||
private bool CanExport() => Results.Count > 0;
|
||||
|
||||
private async Task ExportCsvAsync()
|
||||
private async Task ExportCsvAsync(CancellationToken ct)
|
||||
{
|
||||
if (_csvExportService == null || Results.Count == 0) return;
|
||||
var dialog = new SaveFileDialog
|
||||
@@ -418,9 +470,9 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
try
|
||||
{
|
||||
if (IsSimplifiedMode && SimplifiedResults.Count > 0)
|
||||
await _csvExportService.WriteAsync(SimplifiedResults.ToList(), dialog.FileName, CurrentSplit, CancellationToken.None);
|
||||
await _csvExportService.WriteAsync(SimplifiedResults.ToList(), dialog.FileName, CurrentSplit, ct);
|
||||
else
|
||||
await _csvExportService.WriteAsync((IReadOnlyList<PermissionEntry>)Results, dialog.FileName, CurrentSplit, CancellationToken.None);
|
||||
await _csvExportService.WriteAsync(FilteredResults, dialog.FileName, CurrentSplit, ct);
|
||||
OpenFile(dialog.FileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -430,7 +482,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExportHtmlAsync()
|
||||
private async Task ExportHtmlAsync(CancellationToken ct)
|
||||
{
|
||||
if (_htmlExportService == null || Results.Count == 0) return;
|
||||
var dialog = new SaveFileDialog
|
||||
@@ -458,7 +510,7 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
// by the site it was observed on, then resolve against that
|
||||
// site's context. Using the root tenant ctx for a group that
|
||||
// lives on a sub-site makes CSOM fail with "Group not found".
|
||||
var groupsBySite = Results
|
||||
var groupsBySite = FilteredResults
|
||||
.Where(r => r.PrincipalType == "SharePointGroup")
|
||||
.SelectMany(r => r.Users
|
||||
.Split(';', StringSplitOptions.RemoveEmptyEntries)
|
||||
@@ -488,9 +540,9 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
Name = _currentProfile.Name
|
||||
};
|
||||
var ctx = await _sessionManager.GetOrCreateContextAsync(
|
||||
siteProfile, CancellationToken.None);
|
||||
siteProfile, ct);
|
||||
var resolved = await _groupResolver.ResolveGroupsAsync(
|
||||
ctx, _currentProfile.ClientId, distinctNames, CancellationToken.None);
|
||||
ctx, _currentProfile.ClientId, distinctNames, ct);
|
||||
foreach (var kv in resolved)
|
||||
merged[kv.Key] = kv.Value;
|
||||
}
|
||||
@@ -507,9 +559,9 @@ public partial class PermissionsViewModel : FeatureViewModelBase
|
||||
}
|
||||
|
||||
if (IsSimplifiedMode && SimplifiedResults.Count > 0)
|
||||
await _htmlExportService.WriteAsync(SimplifiedResults.ToList(), dialog.FileName, CurrentSplit, CurrentLayout, CancellationToken.None, branding, groupMembers, HideSystemGroupRaw);
|
||||
await _htmlExportService.WriteAsync(SimplifiedResults.ToList(), dialog.FileName, CurrentSplit, CurrentLayout, ct, branding, groupMembers, HideSystemGroupRaw);
|
||||
else
|
||||
await _htmlExportService.WriteAsync((IReadOnlyList<PermissionEntry>)Results, dialog.FileName, CurrentSplit, CurrentLayout, CancellationToken.None, branding, groupMembers, HideSystemGroupRaw);
|
||||
await _htmlExportService.WriteAsync(FilteredResults, dialog.FileName, CurrentSplit, CurrentLayout, ct, branding, groupMembers, HideSystemGroupRaw);
|
||||
OpenFile(dialog.FileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Reference in New Issue
Block a user