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
This commit is contained in:
@@ -151,6 +151,14 @@ public partial class App : Application
|
|||||||
services.AddTransient<FolderStructureViewModel>();
|
services.AddTransient<FolderStructureViewModel>();
|
||||||
services.AddTransient<FolderStructureView>();
|
services.AddTransient<FolderStructureView>();
|
||||||
|
|
||||||
|
// Phase 7: User Access Audit
|
||||||
|
services.AddTransient<IUserAccessAuditService, UserAccessAuditService>();
|
||||||
|
services.AddTransient<IGraphUserSearchService, GraphUserSearchService>();
|
||||||
|
services.AddTransient<UserAccessCsvExportService>();
|
||||||
|
services.AddTransient<UserAccessHtmlExportService>();
|
||||||
|
services.AddTransient<UserAccessAuditViewModel>();
|
||||||
|
services.AddTransient<UserAccessAuditView>();
|
||||||
|
|
||||||
services.AddSingleton<MainWindow>();
|
services.AddSingleton<MainWindow>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<UserControl x:Class="SharepointToolbox.Views.Tabs.UserAccessAuditView"
|
<UserControl x:Class="SharepointToolbox.Views.Tabs.UserAccessAuditView"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:loc="clr-namespace:SharepointToolbox.Localization"
|
xmlns:loc="clr-namespace:SharepointToolbox.Localization">
|
||||||
xmlns:models="clr-namespace:SharepointToolbox.Core.Models">
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="290" />
|
<ColumnDefinition Width="290" />
|
||||||
@@ -13,22 +12,14 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- Left panel: Audit configuration -->
|
<!-- Left panel -->
|
||||||
<DockPanel Grid.Column="0" Grid.Row="0" Margin="8">
|
<DockPanel Grid.Column="0" Grid.Row="0" Margin="8">
|
||||||
|
|
||||||
<!-- People Picker GroupBox -->
|
|
||||||
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.grp.users]}"
|
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.grp.users]}"
|
||||||
DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8">
|
DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
|
<TextBox Text="{Binding SearchQuery, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,2" />
|
||||||
<!-- Search box -->
|
<TextBlock Text="Searching..." FontStyle="Italic" FontSize="10" Foreground="Gray" Margin="0,0,0,2">
|
||||||
<TextBox Text="{Binding SearchQuery, UpdateSourceTrigger=PropertyChanged}"
|
|
||||||
Margin="0,0,0,2" />
|
|
||||||
|
|
||||||
<!-- Search spinner -->
|
|
||||||
<TextBlock Text="Searching..."
|
|
||||||
Foreground="Gray" FontStyle="Italic" FontSize="11"
|
|
||||||
Margin="0,0,0,2">
|
|
||||||
<TextBlock.Style>
|
<TextBlock.Style>
|
||||||
<Style TargetType="TextBlock">
|
<Style TargetType="TextBlock">
|
||||||
<Setter Property="Visibility" Value="Collapsed" />
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
@@ -40,82 +31,65 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</TextBlock.Style>
|
</TextBlock.Style>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
<ListBox ItemsSource="{Binding SearchResults}" MaxHeight="120"
|
||||||
<!-- Autocomplete results list: shown when SearchResults is not empty -->
|
ScrollViewer.VerticalScrollBarVisibility="Auto" Margin="0,0,0,4">
|
||||||
<ListBox x:Name="SearchResultsList"
|
<ListBox.Style>
|
||||||
ItemsSource="{Binding SearchResults}"
|
<Style TargetType="ListBox">
|
||||||
MaxHeight="160"
|
<Style.Triggers>
|
||||||
BorderThickness="1"
|
<DataTrigger Binding="{Binding SearchResults.Count}" Value="0">
|
||||||
BorderBrush="#CCCCCC"
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
Margin="0,0,0,4"
|
</DataTrigger>
|
||||||
Visibility="Collapsed">
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ListBox.Style>
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Orientation="Vertical" Margin="2">
|
<TextBlock>
|
||||||
<TextBlock Text="{Binding DisplayName}" FontWeight="SemiBold" FontSize="12" />
|
<TextBlock.Text>
|
||||||
<TextBlock Text="{Binding Mail}" Foreground="Gray" FontSize="11" />
|
<MultiBinding StringFormat="{}{0} ({1})">
|
||||||
</StackPanel>
|
<Binding Path="DisplayName" />
|
||||||
|
<Binding Path="Mail" />
|
||||||
|
</MultiBinding>
|
||||||
|
</TextBlock.Text>
|
||||||
|
</TextBlock>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
<ListBox.ItemContainerStyle>
|
<ListBox.InputBindings>
|
||||||
<Style TargetType="ListBoxItem">
|
<MouseBinding MouseAction="LeftClick"
|
||||||
<EventSetter Event="MouseLeftButtonUp" Handler="OnSearchResultClicked" />
|
Command="{Binding AddUserCommand}"
|
||||||
<Setter Property="Padding" Value="4,2" />
|
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=SelectedItem}" />
|
||||||
</Style>
|
</ListBox.InputBindings>
|
||||||
</ListBox.ItemContainerStyle>
|
|
||||||
</ListBox>
|
</ListBox>
|
||||||
|
|
||||||
<!-- Selected users pills (removable) -->
|
|
||||||
<ItemsControl ItemsSource="{Binding SelectedUsers}" Margin="0,0,0,4">
|
<ItemsControl ItemsSource="{Binding SelectedUsers}" Margin="0,0,0,4">
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<WrapPanel />
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Border Background="#D6EAF8"
|
<Border Background="#EBF5FB" BorderBrush="#2980B9" BorderThickness="1"
|
||||||
BorderBrush="#2980B9"
|
CornerRadius="4" Padding="6,2" Margin="0,1">
|
||||||
BorderThickness="1"
|
<DockPanel>
|
||||||
CornerRadius="10"
|
<Button Content="x" DockPanel.Dock="Right" Padding="4,0"
|
||||||
Padding="6,2"
|
Background="Transparent" BorderThickness="0"
|
||||||
Margin="0,0,4,4">
|
Command="{Binding DataContext.RemoveUserCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||||
<StackPanel Orientation="Horizontal">
|
CommandParameter="{Binding}" />
|
||||||
<TextBlock Text="{Binding DisplayName}" FontSize="11" VerticalAlignment="Center" />
|
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center" />
|
||||||
<Button Content="✕"
|
</DockPanel>
|
||||||
Command="{Binding DataContext.RemoveUserCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
|
||||||
CommandParameter="{Binding}"
|
|
||||||
Background="Transparent"
|
|
||||||
BorderThickness="0"
|
|
||||||
FontSize="10"
|
|
||||||
Padding="3,0,0,0"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Cursor="Hand" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
</Border>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
<TextBlock Text="{Binding SelectedUsersLabel}" FontStyle="Italic" Foreground="Gray" FontSize="10" />
|
||||||
<!-- Selected users label -->
|
|
||||||
<TextBlock Text="{Binding SelectedUsersLabel}"
|
|
||||||
FontStyle="Italic" Foreground="Gray" FontSize="11" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
|
||||||
<!-- Site Selection GroupBox -->
|
|
||||||
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.grp.sites]}"
|
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.grp.sites]}"
|
||||||
DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8">
|
DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[btn.view.sites]}"
|
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[btn.view.sites]}"
|
||||||
Command="{Binding OpenSitePickerCommand}"
|
Command="{Binding OpenSitePickerCommand}"
|
||||||
HorizontalAlignment="Left" Padding="8,2" Margin="0,0,0,4" />
|
HorizontalAlignment="Left" Padding="8,2" Margin="0,0,0,4" />
|
||||||
<TextBlock Text="{Binding SitesSelectedLabel}"
|
<TextBlock Text="{Binding SitesSelectedLabel}" FontStyle="Italic" Foreground="Gray" />
|
||||||
FontStyle="Italic" Foreground="Gray" FontSize="11" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
|
||||||
<!-- Scan Options GroupBox -->
|
|
||||||
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.grp.options]}"
|
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.grp.options]}"
|
||||||
DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8">
|
DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
@@ -124,12 +98,11 @@
|
|||||||
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.scan.folders]}"
|
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.scan.folders]}"
|
||||||
IsChecked="{Binding ScanFolders}" Margin="0,0,0,4" />
|
IsChecked="{Binding ScanFolders}" Margin="0,0,0,4" />
|
||||||
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.recursive]}"
|
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.recursive]}"
|
||||||
IsChecked="{Binding IncludeSubsites}" Margin="0,0,0,4" />
|
IsChecked="{Binding IncludeSubsites}" Margin="0,0,0,0" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
|
||||||
<!-- Action buttons -->
|
<StackPanel DockPanel.Dock="Top">
|
||||||
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,4">
|
|
||||||
<Grid Margin="0,0,0,4">
|
<Grid Margin="0,0,0,4">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
@@ -137,12 +110,10 @@
|
|||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Button Grid.Column="0"
|
<Button Grid.Column="0"
|
||||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.btn.run]}"
|
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.btn.run]}"
|
||||||
Command="{Binding RunCommand}"
|
Command="{Binding RunCommand}" Margin="0,0,4,0" Padding="6,3" />
|
||||||
Margin="0,0,4,0" Padding="6,3" />
|
|
||||||
<Button Grid.Column="1"
|
<Button Grid.Column="1"
|
||||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[btn.cancel]}"
|
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[btn.cancel]}"
|
||||||
Command="{Binding CancelCommand}"
|
Command="{Binding CancelCommand}" Margin="0,0,0,0" Padding="6,3" />
|
||||||
Margin="0,0,0,0" Padding="6,3" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@@ -151,74 +122,57 @@
|
|||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Button Grid.Column="0"
|
<Button Grid.Column="0"
|
||||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.btn.exportCsv]}"
|
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.btn.exportCsv]}"
|
||||||
Command="{Binding ExportCsvCommand}"
|
Command="{Binding ExportCsvCommand}" Margin="0,0,4,0" Padding="6,3" />
|
||||||
Margin="0,0,4,0" Padding="6,3" />
|
|
||||||
<Button Grid.Column="1"
|
<Button Grid.Column="1"
|
||||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.btn.exportHtml]}"
|
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.btn.exportHtml]}"
|
||||||
Command="{Binding ExportHtmlCommand}"
|
Command="{Binding ExportHtmlCommand}" Margin="0,0,0,0" Padding="6,3" />
|
||||||
Margin="0,0,0,0" Padding="6,3" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
<!-- Right panel: Summary banner + Results DataGrid -->
|
<!-- Right panel -->
|
||||||
<Grid Grid.Column="1" Grid.Row="0" Margin="0,8,8,8">
|
<Grid Grid.Column="1" Grid.Row="0" Margin="0,8,8,0">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- Summary banner: 3 stat cards -->
|
|
||||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8">
|
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8">
|
||||||
<!-- Total Accesses -->
|
|
||||||
<Border Background="#EBF5FB" BorderBrush="#2980B9" BorderThickness="1"
|
<Border Background="#EBF5FB" BorderBrush="#2980B9" BorderThickness="1"
|
||||||
CornerRadius="4" Padding="12,6" Margin="0,0,8,0" MinWidth="90">
|
CornerRadius="4" Padding="12,6" Margin="0,0,8,0">
|
||||||
<StackPanel HorizontalAlignment="Center">
|
<StackPanel HorizontalAlignment="Center">
|
||||||
<TextBlock Text="{Binding TotalAccessCount}" FontSize="20" FontWeight="Bold"
|
<TextBlock Text="{Binding TotalAccessCount}" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" />
|
||||||
Foreground="#1A5276" HorizontalAlignment="Center" />
|
|
||||||
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.summary.total]}"
|
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.summary.total]}"
|
||||||
FontSize="11" Foreground="#2980B9" HorizontalAlignment="Center" />
|
FontSize="10" Foreground="Gray" HorizontalAlignment="Center" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<!-- Sites -->
|
<Border Background="#EAFAF1" BorderBrush="#27AE60" BorderThickness="1"
|
||||||
<Border Background="#F0F3F4" BorderBrush="#717D7E" BorderThickness="1"
|
CornerRadius="4" Padding="12,6" Margin="0,0,8,0">
|
||||||
CornerRadius="4" Padding="12,6" Margin="0,0,8,0" MinWidth="90">
|
|
||||||
<StackPanel HorizontalAlignment="Center">
|
<StackPanel HorizontalAlignment="Center">
|
||||||
<TextBlock Text="{Binding SitesCount}" FontSize="20" FontWeight="Bold"
|
<TextBlock Text="{Binding SitesCount}" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" />
|
||||||
Foreground="#2C3E50" HorizontalAlignment="Center" />
|
|
||||||
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.summary.sites]}"
|
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.summary.sites]}"
|
||||||
FontSize="11" Foreground="#717D7E" HorizontalAlignment="Center" />
|
FontSize="10" Foreground="Gray" HorizontalAlignment="Center" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<!-- High Privilege -->
|
|
||||||
<Border Background="#FDEDEC" BorderBrush="#E74C3C" BorderThickness="1"
|
<Border Background="#FDEDEC" BorderBrush="#E74C3C" BorderThickness="1"
|
||||||
CornerRadius="4" Padding="12,6" Margin="0,0,0,0" MinWidth="90">
|
CornerRadius="4" Padding="12,6">
|
||||||
<StackPanel HorizontalAlignment="Center">
|
<StackPanel HorizontalAlignment="Center">
|
||||||
<TextBlock Text="{Binding HighPrivilegeCount}" FontSize="20" FontWeight="Bold"
|
<TextBlock Text="{Binding HighPrivilegeCount}" FontSize="20" FontWeight="Bold"
|
||||||
Foreground="#922B21" HorizontalAlignment="Center" />
|
HorizontalAlignment="Center" Foreground="#C0392B" />
|
||||||
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.summary.highPriv]}"
|
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.summary.highPriv]}"
|
||||||
FontSize="11" Foreground="#E74C3C" HorizontalAlignment="Center" />
|
FontSize="10" Foreground="Gray" HorizontalAlignment="Center" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Toolbar: Filter + Group-by toggle -->
|
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,4">
|
||||||
<Grid Grid.Row="1" Margin="0,0,0,6">
|
<TextBox Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}" Width="200" Margin="0,0,8,0" />
|
||||||
<Grid.ColumnDefinitions>
|
<ToggleButton IsChecked="{Binding IsGroupByUser}" Padding="8,3">
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBox Grid.Column="0"
|
|
||||||
Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"
|
|
||||||
Margin="0,0,8,0" />
|
|
||||||
<ToggleButton Grid.Column="1"
|
|
||||||
IsChecked="{Binding IsGroupByUser}">
|
|
||||||
<ToggleButton.Style>
|
<ToggleButton.Style>
|
||||||
<Style TargetType="ToggleButton">
|
<Style TargetType="ToggleButton">
|
||||||
<Setter Property="Content" Value="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.toggle.bySite]}" />
|
<Setter Property="Content" Value="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.toggle.bySite]}" />
|
||||||
<Setter Property="Padding" Value="8,3" />
|
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<Trigger Property="IsChecked" Value="True">
|
<Trigger Property="IsChecked" Value="True">
|
||||||
<Setter Property="Content" Value="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.toggle.byUser]}" />
|
<Setter Property="Content" Value="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[audit.toggle.byUser]}" />
|
||||||
@@ -227,28 +181,21 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</ToggleButton.Style>
|
</ToggleButton.Style>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
</Grid>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Results DataGrid -->
|
<DataGrid Grid.Row="2" ItemsSource="{Binding ResultsView}"
|
||||||
<DataGrid Grid.Row="2"
|
AutoGenerateColumns="False" IsReadOnly="True"
|
||||||
ItemsSource="{Binding ResultsView}"
|
VirtualizingPanel.IsVirtualizing="True" EnableRowVirtualization="True">
|
||||||
AutoGenerateColumns="False"
|
|
||||||
IsReadOnly="True"
|
|
||||||
VirtualizingPanel.IsVirtualizing="True"
|
|
||||||
EnableRowVirtualization="True"
|
|
||||||
CanUserResizeRows="False">
|
|
||||||
|
|
||||||
<!-- Row color-coding by AccessType -->
|
|
||||||
<DataGrid.RowStyle>
|
<DataGrid.RowStyle>
|
||||||
<Style TargetType="DataGridRow">
|
<Style TargetType="DataGridRow">
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<DataTrigger Binding="{Binding AccessType}" Value="{x:Static models:AccessType.Direct}">
|
<DataTrigger Binding="{Binding AccessType}" Value="Direct">
|
||||||
<Setter Property="Background" Value="#EBF5FB" />
|
<Setter Property="Background" Value="#EBF5FB" />
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
<DataTrigger Binding="{Binding AccessType}" Value="{x:Static models:AccessType.Group}">
|
<DataTrigger Binding="{Binding AccessType}" Value="Group">
|
||||||
<Setter Property="Background" Value="#EAFAF1" />
|
<Setter Property="Background" Value="#EAFAF1" />
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
<DataTrigger Binding="{Binding AccessType}" Value="{x:Static models:AccessType.Inherited}">
|
<DataTrigger Binding="{Binding AccessType}" Value="Inherited">
|
||||||
<Setter Property="Background" Value="#F4F6F6" />
|
<Setter Property="Background" Value="#F4F6F6" />
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
<DataTrigger Binding="{Binding IsHighPrivilege}" Value="True">
|
<DataTrigger Binding="{Binding IsHighPrivilege}" Value="True">
|
||||||
@@ -257,155 +204,60 @@
|
|||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</DataGrid.RowStyle>
|
</DataGrid.RowStyle>
|
||||||
|
|
||||||
<!-- Group headers with expander -->
|
|
||||||
<DataGrid.GroupStyle>
|
<DataGrid.GroupStyle>
|
||||||
<GroupStyle>
|
<GroupStyle>
|
||||||
<GroupStyle.ContainerStyle>
|
<GroupStyle.HeaderTemplate>
|
||||||
<Style TargetType="GroupItem">
|
<DataTemplate>
|
||||||
<Setter Property="Template">
|
<StackPanel Orientation="Horizontal" Margin="4,2">
|
||||||
<Setter.Value>
|
<TextBlock Text="{Binding Name}" FontWeight="Bold" Margin="0,0,8,0" />
|
||||||
<ControlTemplate TargetType="GroupItem">
|
<TextBlock Text="{Binding ItemCount, StringFormat=({0})}" Foreground="Gray" />
|
||||||
<Expander IsExpanded="True">
|
</StackPanel>
|
||||||
<Expander.Header>
|
</DataTemplate>
|
||||||
<StackPanel Orientation="Horizontal" Margin="2,2">
|
</GroupStyle.HeaderTemplate>
|
||||||
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" Foreground="#2C3E50" />
|
|
||||||
<TextBlock Text=" (" Foreground="Gray" />
|
|
||||||
<TextBlock Text="{Binding ItemCount}" Foreground="Gray" />
|
|
||||||
<TextBlock Text=")" Foreground="Gray" />
|
|
||||||
</StackPanel>
|
|
||||||
</Expander.Header>
|
|
||||||
<ItemsPresenter />
|
|
||||||
</Expander>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</GroupStyle.ContainerStyle>
|
|
||||||
</GroupStyle>
|
</GroupStyle>
|
||||||
</DataGrid.GroupStyle>
|
</DataGrid.GroupStyle>
|
||||||
|
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="User" Binding="{Binding UserLogin}" Width="160" />
|
||||||
<!-- User column with external user badge -->
|
|
||||||
<DataGridTemplateColumn Header="User" Width="160" SortMemberPath="UserDisplayName">
|
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBlock Text="{Binding UserDisplayName}" VerticalAlignment="Center" />
|
|
||||||
<Border Background="#F0B27A" BorderBrush="#E59866" BorderThickness="1"
|
|
||||||
CornerRadius="8" Padding="4,0" Margin="4,0,0,0">
|
|
||||||
<Border.Style>
|
|
||||||
<Style TargetType="Border">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed" />
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding IsExternalUser}" Value="True">
|
|
||||||
<Setter Property="Visibility" Value="Visible" />
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</Border.Style>
|
|
||||||
<TextBlock Text="Guest" FontSize="9" Foreground="#784212" />
|
|
||||||
</Border>
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
|
||||||
</DataGridTemplateColumn>
|
|
||||||
|
|
||||||
<!-- Site -->
|
|
||||||
<DataGridTextColumn Header="Site" Binding="{Binding SiteTitle}" Width="120" />
|
<DataGridTextColumn Header="Site" Binding="{Binding SiteTitle}" Width="120" />
|
||||||
|
|
||||||
<!-- Object -->
|
|
||||||
<DataGridTextColumn Header="Object" Binding="{Binding ObjectTitle}" Width="140" />
|
<DataGridTextColumn Header="Object" Binding="{Binding ObjectTitle}" Width="140" />
|
||||||
|
<DataGridTextColumn Header="Permission Level" Binding="{Binding PermissionLevel}" Width="120" />
|
||||||
<!-- Permission Level with high-privilege warning icon -->
|
<DataGridTemplateColumn Header="Access Type" Width="110">
|
||||||
<DataGridTemplateColumn Header="Permission Level" Width="130" SortMemberPath="PermissionLevel">
|
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Orientation="Horizontal">
|
<TextBlock>
|
||||||
<TextBlock Text="⚠ " Foreground="#E74C3C" FontSize="12" VerticalAlignment="Center">
|
<TextBlock.Style>
|
||||||
<TextBlock.Style>
|
<Style TargetType="TextBlock">
|
||||||
<Style TargetType="TextBlock">
|
<Setter Property="Text" Value="{Binding AccessType}" />
|
||||||
<Setter Property="Visibility" Value="Collapsed" />
|
<Style.Triggers>
|
||||||
<Style.Triggers>
|
<DataTrigger Binding="{Binding AccessType}" Value="Direct">
|
||||||
<DataTrigger Binding="{Binding IsHighPrivilege}" Value="True">
|
<Setter Property="Foreground" Value="#1A5276" />
|
||||||
<Setter Property="Visibility" Value="Visible" />
|
</DataTrigger>
|
||||||
</DataTrigger>
|
<DataTrigger Binding="{Binding AccessType}" Value="Group">
|
||||||
</Style.Triggers>
|
<Setter Property="Foreground" Value="#1E8449" />
|
||||||
</Style>
|
</DataTrigger>
|
||||||
</TextBlock.Style>
|
<DataTrigger Binding="{Binding AccessType}" Value="Inherited">
|
||||||
</TextBlock>
|
<Setter Property="Foreground" Value="#626567" />
|
||||||
<TextBlock Text="{Binding PermissionLevel}" VerticalAlignment="Center" />
|
</DataTrigger>
|
||||||
</StackPanel>
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
<DataGridTextColumn Header="Granted Through" Binding="{Binding GrantedThrough}" Width="140" />
|
||||||
<!-- Access Type with icon and color -->
|
|
||||||
<DataGridTemplateColumn Header="Access Type" Width="110" SortMemberPath="AccessType">
|
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<!-- Icon based on AccessType -->
|
|
||||||
<TextBlock VerticalAlignment="Center" FontFamily="Segoe UI Symbol" FontSize="13" Margin="0,0,4,0">
|
|
||||||
<TextBlock.Style>
|
|
||||||
<Style TargetType="TextBlock">
|
|
||||||
<Setter Property="Text" Value="→" />
|
|
||||||
<Setter Property="Foreground" Value="#717D7E" />
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding AccessType}" Value="{x:Static models:AccessType.Direct}">
|
|
||||||
<Setter Property="Text" Value="" />
|
|
||||||
<Setter Property="Foreground" Value="#2980B9" />
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding AccessType}" Value="{x:Static models:AccessType.Group}">
|
|
||||||
<Setter Property="Text" Value="" />
|
|
||||||
<Setter Property="Foreground" Value="#27AE60" />
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding AccessType}" Value="{x:Static models:AccessType.Inherited}">
|
|
||||||
<Setter Property="Text" Value="" />
|
|
||||||
<Setter Property="Foreground" Value="#717D7E" />
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</TextBlock.Style>
|
|
||||||
</TextBlock>
|
|
||||||
<!-- Label with color -->
|
|
||||||
<TextBlock Text="{Binding AccessType}" VerticalAlignment="Center">
|
|
||||||
<TextBlock.Style>
|
|
||||||
<Style TargetType="TextBlock">
|
|
||||||
<Setter Property="Foreground" Value="#717D7E" />
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding AccessType}" Value="{x:Static models:AccessType.Direct}">
|
|
||||||
<Setter Property="Foreground" Value="#2980B9" />
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding AccessType}" Value="{x:Static models:AccessType.Group}">
|
|
||||||
<Setter Property="Foreground" Value="#27AE60" />
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</TextBlock.Style>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
|
||||||
</DataGridTemplateColumn>
|
|
||||||
|
|
||||||
<!-- Granted Through -->
|
|
||||||
<DataGridTextColumn Header="Granted Through" Binding="{Binding GrantedThrough}" Width="160" />
|
|
||||||
|
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Bottom: status bar spanning both columns -->
|
<!-- Status bar -->
|
||||||
<StatusBar Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1">
|
<StatusBar Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1">
|
||||||
<StatusBarItem>
|
<StatusBarItem>
|
||||||
<ProgressBar Width="150" Height="14"
|
<ProgressBar Width="150" Height="14" Value="{Binding ProgressValue}" Minimum="0" Maximum="100" />
|
||||||
Value="{Binding ProgressValue}"
|
|
||||||
Minimum="0" Maximum="100" />
|
|
||||||
</StatusBarItem>
|
</StatusBarItem>
|
||||||
<StatusBarItem Content="{Binding StatusMessage}" />
|
<StatusBarItem Content="{Binding StatusMessage}" />
|
||||||
</StatusBar>
|
</StatusBar>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.Collections.Specialized;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using SharepointToolbox.ViewModels.Tabs;
|
using SharepointToolbox.ViewModels.Tabs;
|
||||||
|
|
||||||
@@ -11,31 +9,5 @@ public partial class UserAccessAuditView : UserControl
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContext = viewModel;
|
DataContext = viewModel;
|
||||||
|
|
||||||
// Show/hide the autocomplete list as SearchResults changes
|
|
||||||
viewModel.SearchResults.CollectionChanged += OnSearchResultsChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSearchResultsChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (DataContext is UserAccessAuditViewModel vm)
|
|
||||||
{
|
|
||||||
SearchResultsList.Visibility = vm.SearchResults.Count > 0
|
|
||||||
? Visibility.Visible
|
|
||||||
: Visibility.Collapsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles click on a search result item in the autocomplete list.
|
|
||||||
/// Invokes AddUserCommand on the ViewModel and hides the list.
|
|
||||||
/// </summary>
|
|
||||||
private void OnSearchResultClicked(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is ListBoxItem item && DataContext is UserAccessAuditViewModel vm)
|
|
||||||
{
|
|
||||||
vm.AddUserCommand.Execute(item.DataContext);
|
|
||||||
// AddUserCommand clears SearchResults so CollectionChanged will hide the list
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user