Commit Graph

122 Commits

Author SHA1 Message Date
Dev
e3ff27a673 docs: create milestone v2.3 roadmap (5 phases, 15-19)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:31:54 +02:00
Dev
1a1e83cfad feat(14-02): add directory browse mode UI with mode toggle, DataGrid, and loading UX
- Mode toggle (Search/Browse) RadioButtons at top of left panel
- Search panel uses DataTrigger inverse visibility (collapses when IsBrowseMode=true)
- Browse panel with Load/Cancel buttons, IncludeGuests checkbox, filter TextBox, status/count
- Directory DataGrid with 5 columns (Name, Email, Department, Job Title, Type)
- Guest users highlighted in orange via DataTrigger on UserType
- SelectedUsers extracted to shared section visible in both modes
- DataGrid wired to DirectoryDataGrid_MouseDoubleClick handler
- Scan Options and Run/Export buttons remain always visible

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 09:29:59 +02:00
Dev
d1282cea5d feat(14-01): add DirectoryDataGrid_MouseDoubleClick code-behind handler
- Extracts GraphDirectoryUser from DataGrid.SelectedItem on double-click
- Invokes SelectDirectoryUserCommand to add user to audit pipeline
- Using added for SharepointToolbox.Core.Models

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 09:26:41 +02:00
Dev
e6ba2d8146 feat(14-01): add SelectDirectoryUserCommand bridging directory to audit pipeline
- RelayCommand<GraphDirectoryUser> converts to GraphUserResult and adds to SelectedUsers
- Duplicate UPN check prevents adding same user twice
- Initialized in both DI and test constructors
- 4 new tests pass (add, skip duplicate, null, auditable)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 09:26:12 +02:00
Dev
70e8d121fd feat(14-01): add 14 localization keys for directory browse UI (EN + FR)
- audit.mode.search, audit.mode.browse for mode toggle labels
- directory.grp.browse, directory.btn.load, directory.btn.cancel
- directory.filter.placeholder, directory.chk.guests, directory.status.count
- directory.hint.doubleclick, directory.col.name/upn/department/jobtitle/type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 09:24:54 +02:00
Dev
df6f4949a8 docs(13-02): complete User Directory ViewModel plan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:44:56 +02:00
Dev
4ba4de6106 feat(13-02): add directory browse mode with paginated load, member/guest filter, and sortable ICollectionView
- Inject IGraphUserDirectoryService into UserAccessAuditViewModel (both constructors)
- Add IsBrowseMode toggle, DirectoryUsers collection, DirectoryUsersView with sort/filter
- Add LoadDirectoryCommand with progress reporting, cancellation, and error handling
- Add IncludeGuests toggle for in-memory member/guest filtering (no new Graph request)
- Add DirectoryFilterText for DisplayName/UPN/Department/JobTitle text search
- Add DirectoryUserCount computed property reflecting filtered view count
- Update OnTenantSwitched to clear all directory state
- Add 16 comprehensive unit tests covering all directory browse behaviors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:07:53 +02:00
Dev
9a98371edd feat(13-01): extend GraphDirectoryUser with UserType and add includeGuests parameter to directory service
- Add string? UserType as last positional parameter to GraphDirectoryUser record
- Add bool includeGuests = false parameter to IGraphUserDirectoryService.GetUsersAsync
- Branch Graph filter: members-only (default) vs all users when includeGuests=true
- Add userType to Graph Select array for MapUser population
- Update MapUser to include UserType from Graph User object
- Add MapUser_PopulatesUserType and MapUser_NullUserType tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:01:46 +02:00
Dev
ba81ea3cb7 feat(12-03): add client logo section with live preview to ProfileManagementDialog
- Increase dialog height from 480 to 620 to accommodate logo section
- Add new Row 3 with logo preview, Import/Clear/Pull from Entra buttons
- Image bound to ClientLogoPreview via Base64ToImageConverter
- Placeholder text shown when no logo configured via DataTrigger
- ValidationMessage displays feedback below logo buttons
- All logo buttons auto-disable when no profile selected

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:21:12 +02:00
Dev
b035e91120 feat(12-02): add MSP logo section with live preview to SettingsView
- Add Separator and MSP Logo label after data folder section
- Add Border with Grid containing Image preview and placeholder TextBlock
- Image bound to MspLogoPreview via Base64ToImageConverter with max 80x240
- DataTrigger toggles placeholder visibility when logo is null
- Import/Clear buttons bound to BrowseMspLogoCommand/ClearMspLogoCommand
- StatusMessage TextBlock in red, visible only when set

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:20:47 +02:00
Dev
6a4cd8ab56 feat(12-01): add Base64ToImageSourceConverter, localization keys, and ClientLogoPreview property
- Base64ToImageSourceConverter converts data URI strings to BitmapImage with null-safe error handling
- Registered converter in App.xaml as Base64ToImageConverter global resource
- Added 9 localization keys (EN+FR) for logo UI labels in Settings and Profile dialogs
- Added ClientLogoPreview string property to ProfileManagementViewModel with FormatLogoPreview helper
- Updated OnSelectedProfileChanged, BrowseClientLogoAsync, ClearClientLogoAsync, AutoPullClientLogoAsync
- 17 tests pass (6 converter + 11 profile VM logo tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:18:38 +02:00
Dev
816fb5e3b5 feat(11-03): inject IBrandingService into all 5 export ViewModels and assemble branding in ExportHtmlAsync
- Add IBrandingService field and DI constructor parameter to all 5 ViewModels
- Add optional IBrandingService? parameter to test constructors (PermissionsViewModel, StorageViewModel, UserAccessAuditViewModel)
- Assemble ReportBranding from GetMspLogoAsync + _currentProfile.ClientLogo before each WriteAsync call
- Pass branding as last parameter to WriteAsync in all ExportHtmlAsync methods
- Guard clause: branding assembly skipped (branding = null) when _brandingService is null (test constructors)
- Build: 0 warnings, 0 errors; tests: 254 passed / 0 failed / 26 skipped
2026-04-08 14:50:54 +02:00
Dev
2233fb86a9 feat(11-02): add optional ReportBranding parameter to all 5 HTML export services
- Added ReportBranding? branding = null to BuildHtml on all 5 services
- Added ReportBranding? branding = null after CancellationToken ct on all WriteAsync overloads
- Injected BrandingHtmlHelper.BuildBrandingHeader(branding) between <body> and <h1> in each
- StorageHtmlExportService both overloads updated (nodes-only and nodes+fileTypeMetrics)
- HtmlExportService both overloads updated (PermissionEntry and SimplifiedPermissionEntry)
- Build passes with 0 warnings — all existing callers compile unchanged via default null
2026-04-08 14:44:23 +02:00
Dev
b02b75e5bc feat(11-04): add logo management commands to SettingsViewModel and ProfileManagementViewModel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 14:40:08 +02:00
Dev
212c43915e feat(11-01): add ReportBranding model and BrandingHtmlHelper with tests
- Add ReportBranding positional record bundling MspLogo and ClientLogo
- Add BrandingHtmlHelper static class generating flex branding header HTML
- Add BrandingHtmlHelperTests covering all 4 logo states (null, both null, single, both)
- Add InternalsVisibleTo for SharepointToolbox.Tests in project file
2026-04-08 14:34:45 +02:00
Dev
9e850b07f2 feat(11-04): add UpdateProfileAsync to ProfileService and ImportLogoFromBytesAsync to BrandingService
- ProfileService.UpdateProfileAsync: replaces profile by name and persists the change
- IBrandingService: add ImportLogoFromBytesAsync to interface contract
- BrandingService.ImportLogoFromBytesAsync: validates magic bytes, compresses if > 512KB, returns LogoData
- BrandingService.ImportLogoAsync: refactored to delegate to ImportLogoFromBytesAsync
- ProfileServiceTests: 2 new tests (UpdateProfileAsync happy path + KeyNotFoundException)
- BrandingServiceTests: 2 new tests (ImportLogoFromBytesAsync valid PNG + invalid bytes)
- Tests.csproj: suppress NU1701 for pre-existing LiveCharts2/OpenTK transitive warnings
2026-04-08 14:34:11 +02:00
Dev
e9a1530120 docs(phase-10): complete phase execution and verification
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:30:23 +02:00
Dev
7e8e228155 feat(10-03): register Phase 10 services in DI container
- Add BrandingRepository as Singleton with branding.json path
- Add IBrandingService/BrandingService as Singleton
- Add IGraphUserDirectoryService/GraphUserDirectoryService as Transient
- 224 tests pass, 26 integration tests skipped (live Graph)
2026-04-08 12:36:12 +02:00
Dev
130386622f feat(10-01): create BrandingService with magic byte validation and auto-compression
- Add IBrandingService interface with ImportLogoAsync, Save/Clear/GetMspLogoAsync
- Add BrandingService: PNG/JPEG magic byte detection, rejects unsupported formats with
  descriptive error, auto-compresses files over 512 KB using WPF PresentationCore imaging
- Add BrandingServiceTests: 9 tests covering validation, rejection, compression, CRUD
- Deviation: used WPF BitmapEncoder/TransformedBitmap instead of System.Drawing.Bitmap
  (System.Drawing.Common not available without new NuGet package; WPF PresentationCore
  is in the existing stack per architectural decisions)
2026-04-08 12:32:23 +02:00
Dev
3ba574612f feat(10-02): implement GraphUserDirectoryService with PageIterator and unit tests
- GraphUserDirectoryService uses PageIterator<User, UserCollectionResponse> for pagination
- Filter: accountEnabled eq true and userType eq 'Member' (no ConsistencyLevel header)
- Cancellation checked in PageIterator callback (return false stops iteration)
- Progress reported via IProgress<int> with running count per user
- MapUser extracted as internal static for direct unit test coverage
- Tests: 5 unit tests for MapUser field mapping and fallback logic
- Integration-level tests (pagination/cancellation) skipped with rationale documented
- Note: test project compilation blocked by pre-existing BrandingServiceTests.cs (10-01 artifact)
2026-04-08 12:32:04 +02:00
Dev
2280f12eab feat(10-01): create logo models, BrandingRepository, and repository tests
- Add LogoData record with Base64 and MimeType init properties
- Add BrandingSettings class with nullable MspLogo property
- Extend TenantProfile with nullable ClientLogo property (additive)
- Add BrandingRepository mirroring SettingsRepository pattern (write-then-replace)
- Add BrandingRepositoryTests: 5 tests covering load defaults, round-trip, dir creation, and TenantProfile serialization
2026-04-08 12:29:53 +02:00
Dev
5e56a96cd0 feat(10-02): add GraphDirectoryUser model and IGraphUserDirectoryService interface
- GraphDirectoryUser positional record with DisplayName, UPN, Mail, Department, JobTitle
- IGraphUserDirectoryService.GetUsersAsync with clientId, IProgress<int>?, CancellationToken
- Follows existing GraphUserSearchService namespace pattern
2026-04-08 12:29:19 +02:00
Dev
8447e78db9 docs: start milestone v2.2 Report Branding & User Directory
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 10:57:27 +02:00
Dev
fd442f3b4c chore: archive v1.1 Enhanced Reports milestone
Some checks failed
Release SharePoint Toolbox v2 / release (push) Failing after 14s
v1.1 shipped with 4 phases (25 plans), 10/10 requirements complete:
- Global site selection (toolbar picker, all tabs consume)
- User access audit (Graph people-picker, direct/group/inherited)
- Simplified permissions (plain-language labels, risk levels, detail toggle)
- Storage visualization (LiveCharts2 pie/donut + bar charts)

Post-phase polish: centralized site selection (removed per-tab pickers),
claims prefix stripping, StorageMetrics backfill, chart tooltip fix,
summary stats in app + HTML exports.

205 tests passing, 10,484 LOC.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 10:21:02 +02:00
Dev
a8d79a8241 feat(09-03): add chart panel to StorageView with toggle and localization
- Update StorageView.xaml: DataGrid top, GridSplitter, chart panel bottom
- Add PieChart and CartesianChart with MultiDataTrigger visibility
- Add radio buttons for donut/bar chart toggle in left panel
- Create BytesLabelConverter for chart tooltip formatting
- Add stor.chart.* localization keys in EN and FR resx files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:35:35 +02:00
Dev
70048ddcdf feat(09-03): extend StorageViewModel with chart data properties and toggle
- Add IsDonutChart toggle, FileTypeMetrics collection, PieChartSeries, BarChartSeries, BarXAxes, BarYAxes
- Add UpdateChartSeries method with top-10 + Other aggregation
- Call CollectFileTypeMetricsAsync after storage scan in RunOperationAsync
- Clear chart data on tenant switch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:27:54 +02:00
Dev
81e3dcac6d feat(09-02): implement CollectFileTypeMetricsAsync in StorageService
- CamlQuery with RecursiveAll scope enumerates files across all non-hidden document libraries
- Paginated 500-item batches avoid list view threshold issues
- Files grouped by extension (case-insensitive) with summed size and count
- Results returned as IReadOnlyList<FileTypeMetric> sorted by TotalSizeBytes descending
- Existing CollectStorageAsync, LoadFolderNodeAsync, CollectSubfoldersAsync unchanged

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:24:09 +02:00
Dev
39c31dadfa feat(09-01): extend IStorageService with CollectFileTypeMetricsAsync
- Add CollectFileTypeMetricsAsync method signature to IStorageService
- Returns IReadOnlyList<FileTypeMetric> for chart visualization data
- Existing CollectStorageAsync signature unchanged
- CS0535 expected until StorageService implements in Plan 09-02

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:21:02 +02:00
Dev
60cbb977bf feat(09-01): add LiveCharts2 NuGet and FileTypeMetric data model
- Add LiveChartsCore.SkiaSharpView.WPF 2.0.0-rc5.4 package reference
- Create FileTypeMetric record with Extension, TotalSizeBytes, FileCount
- Include DisplayLabel computed property for chart label binding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:20:38 +02:00
Dev
f503e6c0ca feat(08-05): wire export commands to use simplified overloads
- ExportCsvAsync branches on IsSimplifiedMode to call simplified WriteAsync overload
- ExportHtmlAsync branches on IsSimplifiedMode to call simplified WriteAsync overload
- Standard PermissionEntry export path unchanged when simplified mode is off

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:17:14 +02:00
Dev
60ddcd781f feat(08-05): add EN/FR localization keys for simplified permissions UI
- Add 6 keys to Strings.resx: chk.simplified.mode, grp.display.opts, lbl.detail.level, rad.detail.detailed, rad.detail.simple, lbl.summary.users
- Add matching French translations to Strings.fr.resx with proper XML entities for accented characters
- Wire hardcoded "user(s)" text in PermissionsView.xaml summary cards to lbl.summary.users localization key

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:16:40 +02:00
Dev
899ab7d175 feat(08-04): add simplified export overloads to HtmlExportService
- Add RiskLevelColors helper for risk-level color coding
- Add BuildHtml(IReadOnlyList<SimplifiedPermissionEntry>) with risk summary cards, Simplified column, and color-coded Risk badges
- Add WriteAsync overload for simplified entries
- Original PermissionEntry methods unchanged

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:13:08 +02:00
Dev
163c506e0b feat(08-03): add simplified mode UI to PermissionsView
- Add Display Options GroupBox with Simplified Mode toggle and Simple/Detailed radio buttons
- Add summary panel with color-coded risk level cards bound to Summaries collection
- DataGrid binds to ActiveItemsSource, rows color-coded by RiskLevel via DataTriggers
- SimplifiedLabels column visible only in simplified mode via BooleanToVisibilityConverter
- DataGrid collapses in Simple mode via MultiDataTrigger on IsSimplifiedMode+IsDetailView
- Create InvertBoolConverter for radio button inverse binding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:12:57 +02:00
Dev
fe19249f82 feat(08-04): add simplified export overloads to CsvExportService
- Add BuildCsv(IReadOnlyList<SimplifiedPermissionEntry>) overload with SimplifiedLabels and RiskLevel columns
- Add WriteAsync overload for simplified entries
- Original PermissionEntry methods unchanged

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:12:18 +02:00
Dev
e2c94bf6d1 feat(08-02): add simplified mode properties to PermissionsViewModel
- IsSimplifiedMode toggle switches between raw and simplified labels
- IsDetailView toggle controls individual vs summary row display
- SimplifiedResults and Summaries computed from cached Results
- ActiveItemsSource provides correct collection for DataGrid binding
- Mode toggles rebuild from cache without re-running scan
- OnTenantSwitched resets simplified state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:09:57 +02:00
Dev
6609f2a70a feat(08-01): add SimplifiedPermissionEntry wrapper and PermissionSummary model
- SimplifiedPermissionEntry wraps PermissionEntry with computed labels and risk level
- Passthrough properties preserve DataGrid binding compatibility
- PermissionSummary record for grouped risk-level counts
- PermissionSummaryBuilder always returns all 4 risk levels for consistent UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:06:47 +02:00
Dev
f1390eaa1c feat(08-01): add RiskLevel enum and PermissionLevelMapping helper
- RiskLevel enum with High, Medium, Low, ReadOnly tiers
- PermissionLevelMapping maps 11 standard SharePoint roles to plain-language labels
- Case-insensitive lookup with Medium fallback for unknown roles
- GetHighestRisk and GetSimplifiedLabels for row-level formatting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:06:17 +02:00
Dev
00252fd137 fix(07): fix people picker selection and audit service authentication
People picker ListBox used MouseBinding which fires before SelectedItem
updates, causing null CommandParameter. Replaced with SelectionChanged
event handler in code-behind.

AuditUsersAsync created TenantProfile with empty ClientId, causing
ArgumentException in SessionManager. Added currentProfile parameter
to pass the authenticated tenant's ClientId through.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:44:53 +02:00
Dev
33833dce5d feat(07-09): add guest badge, warning icon, and ObjectType column to DataGrid
- Convert User column to DataGridTemplateColumn with orange 'Guest' pill badge on IsExternalUser=true
- Add ObjectType DataGridTextColumn between Object and Permission Level
- Convert Permission Level column to DataGridTemplateColumn with red warning icon on IsHighPrivilege=true
2026-04-07 13:14:29 +02:00
Dev
a2531ea33f feat(07-07): add localization keys for User Access Audit tab in English and French
- Add 17 audit.* keys and tab.userAccessAudit to Strings.resx (English)
- Add matching French translations with proper Unicode accented characters to Strings.fr.resx
2026-04-07 12:53:37 +02:00
Dev
df796ee956 feat(07-07): add UserAccessAuditTabItem to MainWindow and wire dialog factory
- Add UserAccessAuditTabItem to MainWindow.xaml TabControl before SettingsTabItem
- Wire UserAccessAuditView content and SitePickerDialog factory in MainWindow.xaml.cs
2026-04-07 12:53:04 +02:00
Dev
2ed8a0cb12 feat(07-07): add DI registrations for Phase 7 services and create UserAccessAuditView
- Register IUserAccessAuditService, IGraphUserSearchService, export services, ViewModel and View in App.xaml.cs
- Create UserAccessAuditView.xaml with two-panel layout: people picker, site picker, scan options, color-coded DataGrid with grouping, summary banner
- Create UserAccessAuditView.xaml.cs code-behind with ViewModel constructor injection
- [Rule 3] UserAccessAuditView was missing (07-05 not executed); created inline to unblock 07-07
2026-04-07 12:52:36 +02:00
Dev
975762dee4 feat(07-05): create UserAccessAuditView code-behind
- UserControl with UserAccessAuditViewModel constructor injection, sets DataContext
- Wires SearchResults.CollectionChanged to show/hide autocomplete ListBox
- OnSearchResultClicked handler invokes AddUserCommand for mouse-based user selection
2026-04-07 12:49:41 +02:00
Dev
bb9ba9d310 feat(07-05): create UserAccessAuditView XAML layout
- Two-panel layout (290px left + * right) following PermissionsView pattern
- Left panel: people picker with autocomplete list + removable user pills, site picker button, scan option checkboxes, run/cancel/export buttons
- Right panel: 3-card summary banner (TotalAccessCount, SitesCount, HighPrivilegeCount), filter TextBox, group-by ToggleButton, color-coded DataGrid
- DataGrid: color-coded rows by AccessType (Direct=blue, Group=green, Inherited=gray), warning icon for high privilege, Guest badge for external users, access type icons
- GroupStyle with Expander headers showing group name + item count
- Status bar with ProgressBar + StatusMessage
2026-04-07 12:49:37 +02:00
Dev
3de737ac3f feat(07-04): implement UserAccessAuditViewModel
- Extends FeatureViewModelBase with RunOperationAsync calling IUserAccessAuditService.AuditUsersAsync
- People picker with 300ms debounced Graph search via IGraphUserSearchService.SearchUsersAsync
- SelectedUsers ObservableCollection<GraphUserResult> with AddUserCommand/RemoveUserCommand
- Results ObservableCollection<UserAccessEntry> with CollectionViewSource grouping (by user/site) and FilterText predicate
- Summary banner properties: TotalAccessCount, SitesCount, HighPrivilegeCount (computed from Results)
- ExportCsvCommand/ExportHtmlCommand using UserAccessCsvExportService/UserAccessHtmlExportService
- Site selection with _hasLocalSiteOverride + OnGlobalSitesChanged pattern from PermissionsViewModel
- Dual constructors (DI + internal test constructor omitting export services)
- OnTenantSwitched resets all state (results, users, search, sites)
2026-04-07 12:44:02 +02:00
Dev
3146a04ad8 feat(07-06): implement UserAccessHtmlExportService
- BuildHtml produces self-contained HTML with inline CSS and JS
- Stats cards: Total Accesses, Users Audited, Sites Scanned, High Privilege, External Users
- Per-user summary cards with high-privilege border highlight and guest badge
- Dual-view toggle (By User / By Site) with JS toggleView()
- Collapsible group headers per user and per site via toggleGroup()
- Sortable columns via sortTable() within each group
- Text filter via filterTable() scoping to active view
- Color-coded access type badges: Direct (blue), Group (green), Inherited (gray)
- High-privilege rows with bold text and warning icon
- External user guest badge (orange pill)
- UTF-8 without BOM encoding (matching HtmlExportService pattern)
2026-04-07 12:40:51 +02:00
Dev
44b238e07a feat(07-02): implement UserAccessAuditService
- Scans permissions via IPermissionsService.ScanSiteAsync per site
- Filters PermissionEntry results to matching target user logins (case-insensitive contains)
- Splits semicolon-delimited users/logins/levels into per-user UserAccessEntry rows
- Classifies AccessType: Inherited (!HasUniquePermissions), Group (GrantedThrough), Direct
- Flags IsHighPrivilege (Full Control, Site Collection Administrator) and IsExternalUser (#EXT#)
2026-04-07 12:39:57 +02:00
Dev
9f891aa512 feat(07-06): implement UserAccessCsvExportService
- BuildCsv per-user CSV with summary section (user, totals, sites, high-privilege, date)
- WriteAsync groups entries by UserLogin, writes one file per user (audit_{email}_{date}.csv)
- WriteSingleFileAsync combines all users in one file for SaveFileDialog export
- RFC 4180 CSV escaping, UTF-8 with BOM for Excel compatibility
- SanitizeFileName strips invalid path chars from email addresses
2026-04-07 12:39:35 +02:00
Dev
026b8294de feat(07-03): implement GraphUserSearchService for people-picker autocomplete
- Queries Graph /users with startsWith filter on displayName, mail, UPN
- Requires minimum 2 chars to prevent overly broad queries
- Sets ConsistencyLevel=eventual + Count=true (required for advanced filter)
- Escapes single quotes to prevent OData injection
- Returns up to maxResults (default 10) GraphUserResult records
2026-04-07 12:39:22 +02:00
Dev
1a6989a9bb feat(07-01): add IUserAccessAuditService and IGraphUserSearchService interfaces
- IUserAccessAuditService.AuditUsersAsync: scan sites and filter by user logins
- IGraphUserSearchService.SearchUsersAsync: Graph API people-picker autocomplete
- GraphUserResult record: DisplayName, UserPrincipalName, Mail
2026-04-07 12:37:26 +02:00