diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index bb2581c..fa5981e 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -48,24 +48,24 @@ Requirements for v2.3 Tenant Management & Report Enhancements. Each maps to road | Requirement | Phase | Status | |-------------|-------|--------| -| APPREG-01 | — | Pending | -| APPREG-02 | — | Pending | -| APPREG-03 | — | Pending | -| APPREG-04 | — | Pending | -| APPREG-05 | — | Pending | -| APPREG-06 | — | Pending | -| OWN-01 | — | Pending | -| OWN-02 | — | Pending | -| RPT-01 | — | Pending | -| RPT-02 | — | Pending | -| RPT-03 | — | Pending | -| RPT-04 | — | Pending | +| APPREG-01 | Phase 19 | Pending | +| APPREG-02 | Phase 19 | Pending | +| APPREG-03 | Phase 19 | Pending | +| APPREG-04 | Phase 19 | Pending | +| APPREG-05 | Phase 19 | Pending | +| APPREG-06 | Phase 19 | Pending | +| OWN-01 | Phase 18 | Pending | +| OWN-02 | Phase 18 | Pending | +| RPT-01 | Phase 17 | Pending | +| RPT-02 | Phase 17 | Pending | +| RPT-03 | Phase 16 | Pending | +| RPT-04 | Phase 15 | Pending | **Coverage:** - v2.3 requirements: 12 total -- Mapped to phases: 0 -- Unmapped: 12 +- Mapped to phases: 12 +- Unmapped: 0 --- *Requirements defined: 2026-04-09* -*Last updated: 2026-04-09 after initial definition* +*Last updated: 2026-04-09 after roadmap created* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 2db97c8..b633085 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -5,6 +5,7 @@ - ✅ **v1.0 MVP** — Phases 1-5 (shipped 2026-04-07) — [archive](milestones/v1.0-ROADMAP.md) - ✅ **v1.1 Enhanced Reports** — Phases 6-9 (shipped 2026-04-08) — [archive](milestones/v1.1-ROADMAP.md) - ✅ **v2.2 Report Branding & User Directory** — Phases 10-14 (shipped 2026-04-09) — [archive](milestones/v2.2-ROADMAP.md) +- 🔄 **v2.3 Tenant Management & Report Enhancements** — Phases 15-19 (in progress) ## Phases @@ -40,6 +41,72 @@ +### v2.3 Tenant Management & Report Enhancements (Phases 15-19) + +- [ ] **Phase 15: Consolidation Data Model** — PermissionConsolidator service and merged-row model; zero API calls, pure data shapes +- [ ] **Phase 16: Report Consolidation Toggle** — Export settings toggle wired to PermissionConsolidator; first user-visible consolidation behavior +- [ ] **Phase 17: Group Expansion in HTML Reports** — Clickable group expansion in HTML exports with transitive membership resolution +- [ ] **Phase 18: Auto-Take Ownership** — Global toggle and automatic site collection admin elevation on access denied +- [ ] **Phase 19: App Registration & Removal** — Automated Entra app registration with guided fallback and clean removal + +## Phase Details + +### Phase 15: Consolidation Data Model +**Goal**: The data shape and merge logic for report consolidation exist and are fully testable in isolation before any UI touches them +**Depends on**: Nothing (no API calls, no UI dependencies) +**Requirements**: RPT-04 +**Success Criteria** (what must be TRUE): + 1. A `ConsolidatedPermissionEntry` model exists that represents a single user's merged access across multiple locations with identical access levels + 2. A `PermissionConsolidator` service accepts a flat list of permission rows and returns a consolidated list where duplicate user+level rows are merged + 3. Consolidation logic has unit test coverage — a known 10-row input with 3 duplicate pairs produces the expected 7-row output + 4. Existing HTML export services compile and produce identical output when consolidation is not applied (opt-in, defaults off) +**Plans**: TBD + +### Phase 16: Report Consolidation Toggle +**Goal**: Users can choose to merge duplicate permission entries per export through a toggle in the export settings dialog +**Depends on**: Phase 15 +**Requirements**: RPT-03 +**Success Criteria** (what must be TRUE): + 1. A consolidation toggle is visible in the export settings dialog (or export options panel) and defaults to OFF + 2. When the toggle is OFF, the exported HTML report is byte-for-byte identical to the pre-v2.3 output + 3. When the toggle is ON, the exported HTML report merges rows for the same user with identical access levels into a single row showing all affected locations + 4. The toggle state is remembered for the session (does not reset between exports within the same session) +**Plans**: TBD + +### Phase 17: Group Expansion in HTML Reports +**Goal**: Users can expand SharePoint group entries in HTML reports to see the group's members, including members of nested groups +**Depends on**: Phase 16 +**Requirements**: RPT-01, RPT-02 +**Success Criteria** (what must be TRUE): + 1. SharePoint group rows in the HTML report render as expandable — clicking a group name reveals its member list inline + 2. Member resolution includes transitive membership: nested groups are recursively resolved so every leaf user is shown + 3. Group expansion is triggered at export time via Graph API — the permission scan itself is unchanged + 4. When Graph cannot resolve a group's members (throttled or insufficient scope), the report shows the group row with a "members unavailable" label rather than failing the export +**Plans**: TBD + +### Phase 18: Auto-Take Ownership +**Goal**: Users can enable automatic site collection admin elevation so that access-denied sites during scans no longer block audit progress +**Depends on**: Phase 15 +**Requirements**: OWN-01, OWN-02 +**Success Criteria** (what must be TRUE): + 1. A global "Auto-take ownership on access denied" toggle exists in application settings and defaults to OFF + 2. When the toggle is OFF, access-denied sites produce the same error behavior as before v2.3 (no regression) + 3. When the toggle is ON and a scan hits access denied on a site, the app automatically calls `Tenant.SetSiteAdmin` to elevate ownership and retries the site without interrupting the scan + 4. The scan result for an auto-elevated site is visually distinguishable from a normally-scanned site (e.g., a flag or icon in the results) +**Plans**: TBD + +### Phase 19: App Registration & Removal +**Goal**: Users can register and remove the Toolbox's Azure AD application on a target tenant directly from the profile dialog, with a guided fallback when permissions are insufficient +**Depends on**: Phase 18 +**Requirements**: APPREG-01, APPREG-02, APPREG-03, APPREG-04, APPREG-05, APPREG-06 +**Success Criteria** (what must be TRUE): + 1. A "Register App" action is available in the profile create/edit dialog and is the recommended path for new tenant onboarding + 2. Before attempting registration, the app checks for Global Admin role and surfaces a clear message if the signed-in user lacks the required permissions, then presents step-by-step manual registration instructions as a fallback + 3. Registration creates the Azure AD application, service principal, and grants all required API permissions in a single atomic operation — if any step fails, all partial changes are rolled back and the user sees a specific error explaining what failed and why + 4. A "Remove App" action in the profile dialog removes the Azure AD application registration from the target tenant + 5. After removal, all cached MSAL tokens and session state for that tenant are cleared, and subsequent operations require re-authentication +**Plans**: TBD + ## Progress | Phase | Milestone | Plans | Status | Completed | @@ -47,3 +114,8 @@ | 1-5 | v1.0 | 36/36 | Shipped | 2026-04-07 | | 6-9 | v1.1 | 25/25 | Shipped | 2026-04-08 | | 10-14 | v2.2 | 14/14 | Shipped | 2026-04-09 | +| 15. Consolidation Data Model | v2.3 | 0/? | Not started | — | +| 16. Report Consolidation Toggle | v2.3 | 0/? | Not started | — | +| 17. Group Expansion in HTML Reports | v2.3 | 0/? | Not started | — | +| 18. Auto-Take Ownership | v2.3 | 0/? | Not started | — | +| 19. App Registration & Removal | v2.3 | 0/? | Not started | — | diff --git a/.planning/STATE.md b/.planning/STATE.md index bc11d07..5df8dd9 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,12 +2,12 @@ gsd_state_version: 1.0 milestone: v2.3 milestone_name: Tenant Management & Report Enhancements -status: defining-requirements -stopped_at: milestone started +status: roadmap-ready +stopped_at: roadmap created — ready for phase 15 planning last_updated: "2026-04-09" -last_activity: 2026-04-09 — Milestone v2.3 started +last_activity: 2026-04-09 — Roadmap created for v2.3 (phases 15-19) progress: - total_phases: 0 + total_phases: 5 completed_phases: 0 total_plans: 0 completed_plans: 0 @@ -20,14 +20,18 @@ progress: See: .planning/PROJECT.md (updated 2026-04-09) **Core value:** Administrators can audit and manage SharePoint/Teams permissions and storage across multiple client tenants from a single, reliable desktop application. -**Current focus:** v2.3 Tenant Management & Report Enhancements +**Current focus:** v2.3 Tenant Management & Report Enhancements — Phase 15 next ## Current Position -Phase: Not started (defining requirements) +Phase: 15 — Consolidation Data Model (not started) Plan: — -Status: Defining requirements -Last activity: 2026-04-09 — Milestone v2.3 started +Status: Roadmap approved — ready to plan Phase 15 +Last activity: 2026-04-09 — Roadmap created for v2.3 (phases 15-19) + +``` +v2.3 Progress: ░░░░░░░░░░ 0% (0/5 phases) +``` ## Shipped Milestones @@ -35,12 +39,29 @@ Last activity: 2026-04-09 — Milestone v2.3 started - v1.1 Enhanced Reports — Phases 6-9 (shipped 2026-04-08) - v2.2 Report Branding & User Directory — Phases 10-14 (shipped 2026-04-09) +## v2.3 Phase Map + +| Phase | Name | Requirements | Status | +|-------|------|--------------|--------| +| 15 | Consolidation Data Model | RPT-04 | Not started | +| 16 | Report Consolidation Toggle | RPT-03 | Not started | +| 17 | Group Expansion in HTML Reports | RPT-01, RPT-02 | Not started | +| 18 | Auto-Take Ownership | OWN-01, OWN-02 | Not started | +| 19 | App Registration & Removal | APPREG-01..06 | Not started | + ## Accumulated Context ### Decisions Decisions are logged in PROJECT.md Key Decisions table. +**v2.3 notable constraints:** +- Phase 19 has the highest blast radius (Entra changes) — must be last +- Phase 15 is zero-API-call foundation; unblocks Phase 16 (consolidation) and Phase 18 (ownership) independently +- Group expansion (Phase 17) calls Graph at export time, not at scan time — scan pipeline unchanged +- Auto-take ownership uses PnP `Tenant.SetSiteAdmin` — requires Tenant Admin scope +- App registration must be atomic with rollback; partial Entra state is worse than no state + ### Pending Todos None. @@ -52,6 +73,6 @@ None. ## Session Continuity Last session: 2026-04-09 -Stopped at: Milestone v2.3 started — defining requirements +Stopped at: Roadmap created — ready to plan Phase 15 Resume file: None -Next step: Define requirements, then create roadmap +Next step: `/gsd:plan-phase 15` diff --git a/.planning/phases/14-user-directory-view/14-01-PLAN.md b/.planning/phases/14-user-directory-view/14-01-PLAN.md new file mode 100644 index 0000000..9d6538c --- /dev/null +++ b/.planning/phases/14-user-directory-view/14-01-PLAN.md @@ -0,0 +1,275 @@ +--- +phase: 14-user-directory-view +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - SharepointToolbox/Localization/Strings.resx + - SharepointToolbox/Localization/Strings.fr.resx + - SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs + - SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml.cs + - SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelDirectoryTests.cs +autonomous: true +requirements: + - UDIR-05 + - UDIR-01 + +must_haves: + truths: + - "SelectDirectoryUserCommand takes a GraphDirectoryUser, converts it to GraphUserResult, adds it to SelectedUsers via existing logic" + - "After SelectDirectoryUserCommand, the user appears in SelectedUsers and can be audited with RunCommand" + - "SelectDirectoryUserCommand does not add duplicates (same UPN check as existing AddUserCommand)" + - "Localization keys for directory UI exist in both EN and FR resource files" + - "Code-behind has a DirectoryDataGrid_MouseDoubleClick handler that invokes SelectDirectoryUserCommand" + artifacts: + - path: "SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs" + provides: "SelectDirectoryUserCommand bridging directory selection to audit pipeline" + contains: "SelectDirectoryUserCommand" + - path: "SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml.cs" + provides: "Event handler for directory DataGrid double-click" + contains: "DirectoryDataGrid_MouseDoubleClick" + - path: "SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelDirectoryTests.cs" + provides: "Tests for SelectDirectoryUserCommand" + contains: "SelectDirectoryUser" + key_links: + - from: "SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs" + to: "SharepointToolbox/Core/Models/GraphDirectoryUser.cs" + via: "command parameter type" + pattern: "GraphDirectoryUser" + - from: "SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml.cs" + to: "SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs" + via: "command invocation" + pattern: "SelectDirectoryUserCommand" +--- + + +Add localization keys for directory UI, the SelectDirectoryUserCommand that bridges directory selection to the audit pipeline, and a code-behind event handler for DataGrid double-click. + +Purpose: Provides the infrastructure (localization, command, event handler) that Plan 14-02 needs to build the XAML view. SC2 requires selecting a directory user to trigger an audit — this command makes that possible. + +Output: Localization keys (EN+FR), SelectDirectoryUserCommand with tests, code-behind event handler. + + + +@C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md +@C:/Users/dev/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/phases/14-user-directory-view/14-RESEARCH.md + + + +From SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs: +```csharp +public RelayCommand AddUserCommand { get; } + +private void ExecuteAddUser(GraphUserResult? user) +{ + if (user == null) return; + if (!SelectedUsers.Any(u => u.UserPrincipalName == user.UserPrincipalName)) + { + SelectedUsers.Add(user); + } + SearchQuery = string.Empty; + SearchResults.Clear(); +} +``` + + +From SharepointToolbox/Core/Models/GraphDirectoryUser.cs: +```csharp +public record GraphDirectoryUser( + string DisplayName, string UserPrincipalName, + string? Mail, string? Department, string? JobTitle, string? UserType); +``` + + +From SharepointToolbox/Services/IGraphUserSearchService.cs: +```csharp +public record GraphUserResult(string DisplayName, string UserPrincipalName, string? Mail); +``` + + +From SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml.cs: +```csharp +private void SearchResultsListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) +{ + if (sender is ListBox listBox && listBox.SelectedItem is GraphUserResult user) + { + var vm = (UserAccessAuditViewModel)DataContext; + if (vm.AddUserCommand.CanExecute(user)) + vm.AddUserCommand.Execute(user); + listBox.SelectedItem = null; + } +} +``` + + +From Strings.resx: +```xml +Select Users +Run Audit +``` + + + + + + + Task 1: Add localization keys for directory UI (EN + FR) + + SharepointToolbox/Localization/Strings.resx, + SharepointToolbox/Localization/Strings.fr.resx + + + - Both resx files contain matching keys for directory browse UI + + + 1. Add to `Strings.resx` (EN): + - `audit.mode.search` = "Search" + - `audit.mode.browse` = "Browse Directory" + - `directory.grp.browse` = "User Directory" + - `directory.btn.load` = "Load Directory" + - `directory.btn.cancel` = "Cancel" + - `directory.filter.placeholder` = "Filter users..." + - `directory.chk.guests` = "Include guests" + - `directory.status.count` = "users" + - `directory.hint.doubleclick` = "Double-click a user to add to audit" + - `directory.col.name` = "Name" + - `directory.col.upn` = "Email" + - `directory.col.department` = "Department" + - `directory.col.jobtitle` = "Job Title" + - `directory.col.type` = "Type" + + 2. Add to `Strings.fr.resx` (FR): + - `audit.mode.search` = "Recherche" + - `audit.mode.browse` = "Parcourir l'annuaire" + - `directory.grp.browse` = "Annuaire utilisateurs" + - `directory.btn.load` = "Charger l'annuaire" + - `directory.btn.cancel` = "Annuler" + - `directory.filter.placeholder` = "Filtrer les utilisateurs..." + - `directory.chk.guests` = "Inclure les invités" + - `directory.status.count` = "utilisateurs" + - `directory.hint.doubleclick` = "Double-cliquez sur un utilisateur pour l'ajouter à l'audit" + - `directory.col.name` = "Nom" + - `directory.col.upn` = "Courriel" + - `directory.col.department` = "Département" + - `directory.col.jobtitle` = "Poste" + - `directory.col.type` = "Type" + + + dotnet build --no-restore -warnaserror + + 14 localization keys present in both EN and FR resource files. + + + + Task 2: Add SelectDirectoryUserCommand to ViewModel + + SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs, + SharepointToolbox.Tests/ViewModels/UserAccessAuditViewModelDirectoryTests.cs + + + - SelectDirectoryUserCommand is a RelayCommand + - It converts GraphDirectoryUser to GraphUserResult and adds to SelectedUsers + - Duplicate UPN check (same as AddUserCommand) + - Does NOT clear SearchQuery/SearchResults (not in search mode context) + - After execution, IsBrowseMode stays true — user can continue selecting from directory + + + 1. Add command declaration in ViewModel: + ```csharp + public RelayCommand SelectDirectoryUserCommand { get; } + ``` + + 2. Initialize in BOTH constructors: + ```csharp + SelectDirectoryUserCommand = new RelayCommand(ExecuteSelectDirectoryUser); + ``` + + 3. Implement the command method: + ```csharp + private void ExecuteSelectDirectoryUser(GraphDirectoryUser? dirUser) + { + if (dirUser == null) return; + var userResult = new GraphUserResult(dirUser.DisplayName, dirUser.UserPrincipalName, dirUser.Mail); + if (!SelectedUsers.Any(u => u.UserPrincipalName == userResult.UserPrincipalName)) + { + SelectedUsers.Add(userResult); + } + } + ``` + + 4. Add tests to `UserAccessAuditViewModelDirectoryTests.cs`: + - Test: SelectDirectoryUserCommand adds user to SelectedUsers + - Test: SelectDirectoryUserCommand skips duplicates + - Test: SelectDirectoryUserCommand with null does nothing + - Test: After SelectDirectoryUser, user can be audited with RunCommand (integration: add user + check SelectedUsers.Count > 0) + + + dotnet build --no-restore -warnaserror && dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~UserAccessAuditViewModelDirectory" --no-build -q + + SelectDirectoryUserCommand bridges directory selection to audit pipeline. Tests pass. + + + + Task 3: Add code-behind event handler for directory DataGrid + + SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml.cs + + + - DirectoryDataGrid_MouseDoubleClick handler extracts the clicked GraphDirectoryUser + - Invokes SelectDirectoryUserCommand with the selected user + - Uses the same pattern as SearchResultsListBox_SelectionChanged + + + 1. Add to `UserAccessAuditView.xaml.cs`: + ```csharp + private void DirectoryDataGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + if (sender is DataGrid grid && grid.SelectedItem is GraphDirectoryUser user) + { + var vm = (UserAccessAuditViewModel)DataContext; + if (vm.SelectDirectoryUserCommand.CanExecute(user)) + vm.SelectDirectoryUserCommand.Execute(user); + } + } + ``` + + 2. Add the required using statement if not present: + ```csharp + using System.Windows.Controls; // Already present + using SharepointToolbox.Core.Models; // For GraphDirectoryUser + ``` + + + dotnet build --no-restore -warnaserror + + Code-behind event handler exists, ready to be wired in XAML (Plan 14-02). + + + + + +```bash +dotnet build --no-restore -warnaserror +dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~UserAccessAuditViewModelDirectory" --no-build -q +``` +Both must pass with zero failures. + + + +- 14 localization keys in both EN and FR resx files +- SelectDirectoryUserCommand converts GraphDirectoryUser → GraphUserResult → SelectedUsers +- Duplicate UPN check prevents adding same user twice +- Code-behind event handler for DataGrid double-click +- All tests pass, build clean + + + +After completion, create `.planning/phases/14-user-directory-view/14-01-SUMMARY.md` + diff --git a/.planning/phases/14-user-directory-view/14-02-PLAN.md b/.planning/phases/14-user-directory-view/14-02-PLAN.md new file mode 100644 index 0000000..fb3d737 --- /dev/null +++ b/.planning/phases/14-user-directory-view/14-02-PLAN.md @@ -0,0 +1,338 @@ +--- +phase: 14-user-directory-view +plan: 02 +type: execute +wave: 2 +depends_on: [14-01] +files_modified: + - SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml +autonomous: true +requirements: + - UDIR-05 + - UDIR-01 + +must_haves: + truths: + - "The left panel shows a mode toggle (two RadioButtons: Search / Browse Directory) at the top" + - "When Search mode is selected (IsBrowseMode=false), the existing people-picker GroupBox is visible and the directory panel is collapsed" + - "When Browse mode is selected (IsBrowseMode=true), the directory panel is visible and the people-picker GroupBox is collapsed" + - "The Scan Options GroupBox and Run/Export buttons remain visible in both modes" + - "The directory panel contains: Load Directory button, Cancel button, Include guests checkbox, filter TextBox, status text, user count, and a DataGrid" + - "The DataGrid is bound to DirectoryUsersView with columns: Name, Email, Department, Job Title, Type" + - "The DataGrid has MouseDoubleClick wired to DirectoryDataGrid_MouseDoubleClick code-behind handler" + - "While loading, the status text shows DirectoryLoadStatus and Load button is disabled" + - "A hint text tells users to double-click to add a user to the audit" + - "The SelectedUsers ItemsControl remains visible in both modes (users added from directory appear here)" + artifacts: + - path: "SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml" + provides: "Complete directory browse UI with mode toggle, directory DataGrid, and loading UX" + contains: "DirectoryUsersView" + key_links: + - from: "SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml" + to: "SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs" + via: "data binding" + pattern: "IsBrowseMode|DirectoryUsersView|LoadDirectoryCommand|DirectoryFilterText|IncludeGuests" +--- + + +Add the complete directory browse UI to UserAccessAuditView.xaml with mode toggle, directory DataGrid, loading indicators, and seamless integration with the existing audit workflow. + +Purpose: SC1-SC4 require visible UI for mode switching, directory display, loading progress, and cancellation. This plan wires all Phase 13 ViewModel properties to the View layer. + +Output: Updated UserAccessAuditView.xaml with full directory browse mode. + + + +@C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md +@C:/Users/dev/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/phases/14-user-directory-view/14-RESEARCH.md + + + +From SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs: +```csharp +// Mode toggle +public bool IsBrowseMode { get; set; } + +// Directory data +public ObservableCollection DirectoryUsers { get; } +public ICollectionView DirectoryUsersView { get; } // filtered + sorted +public int DirectoryUserCount { get; } // computed filtered count + +// Directory commands +public IAsyncRelayCommand LoadDirectoryCommand { get; } +public RelayCommand CancelDirectoryLoadCommand { get; } +public RelayCommand SelectDirectoryUserCommand { get; } + +// Directory state +public bool IsLoadingDirectory { get; } +public string DirectoryLoadStatus { get; } +public bool IncludeGuests { get; set; } +public string DirectoryFilterText { get; set; } + +// Existing (still visible in both modes) +public ObservableCollection SelectedUsers { get; } +public string SelectedUsersLabel { get; } +public IAsyncRelayCommand RunCommand { get; } +public RelayCommand CancelCommand { get; } +public IAsyncRelayCommand ExportCsvCommand { get; } +public IAsyncRelayCommand ExportHtmlCommand { get; } +``` + + +- `{StaticResource BoolToVisibilityConverter}` — true→Visible, false→Collapsed +- `{StaticResource InverseBoolConverter}` — inverts bool +- `{StaticResource StringToVisibilityConverter}` — non-empty→Visible + + +``` +DockPanel (290px, Margin 8) +├── GroupBox "Select Users" (DockPanel.Dock="Top") — SEARCH MODE (hide when IsBrowseMode) +│ └── SearchQuery, SearchResults, SelectedUsers, SelectedUsersLabel +├── GroupBox "Scan Options" (DockPanel.Dock="Top") — ALWAYS VISIBLE +│ └── CheckBoxes +└── StackPanel (DockPanel.Dock="Top") — ALWAYS VISIBLE + └── Run/Cancel/Export buttons +``` + + +```csharp +private void DirectoryDataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) +{ + if (sender is DataGrid grid && grid.SelectedItem is GraphDirectoryUser user) + { + var vm = (UserAccessAuditViewModel)DataContext; + if (vm.SelectDirectoryUserCommand.CanExecute(user)) + vm.SelectDirectoryUserCommand.Execute(user); + } +} +``` + + + + + + + Task 1: Restructure left panel with mode toggle and conditional panels + + SharepointToolbox/Views/Tabs/UserAccessAuditView.xaml + + + - At the top of the left panel DockPanel, a mode toggle section appears with two RadioButtons + - RadioButton "Search" is checked when IsBrowseMode=false (uses InverseBoolConverter) + - RadioButton "Browse Directory" is checked when IsBrowseMode=true + - Below the toggle: existing Search GroupBox (visible when IsBrowseMode=false) OR new Directory GroupBox (visible when IsBrowseMode=true) + - SelectedUsers ItemsControl + label extracted from Search GroupBox and placed in a shared section visible in BOTH modes + - Scan Options GroupBox and buttons remain always visible + - Directory GroupBox contains: + a) Two-button grid: Load Directory + Cancel (like Run/Cancel pattern) + b) CheckBox for IncludeGuests + c) Filter TextBox bound to DirectoryFilterText + d) Status/count row: DirectoryLoadStatus + DirectoryUserCount + e) DataGrid bound to DirectoryUsersView with 5 columns (Name, Email, Department, Job Title, Type) + f) Hint text: "Double-click a user to add to audit" + - DataGrid has MouseDoubleClick="DirectoryDataGrid_MouseDoubleClick" + - DataGrid uses AutoGenerateColumns="False", IsReadOnly="True", virtualization enabled + - DataGrid columns are DataGridTextColumn (simple text, sortable by default) + - Guest users highlighted with a subtle "Guest" badge in the Type column (orange, like the existing UserAccessAuditView pattern) + + + 1. Read the current `UserAccessAuditView.xaml` to get the exact current content. + + 2. Replace the left panel DockPanel content with the new structure: + + ```xml + + + + + + + + + + + + + + [existing DataTrigger style] + + + + + + + + + + + + + + + +